Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1462,10 +1462,11 @@ ]) AC_CHECK_MEMBER(struct sockaddr_un.sun_path, [ AC_DEFINE(OF_HAVE_UNIX_SOCKETS, 1, [Whether we have UNIX sockets]) + AC_SUBST(USE_SRCS_UNIX_SOCKETS, '${SRCS_UNIX_SOCKETS}') ], [], [ #ifdef OF_HAVE_SYS_UN_H # include #endif ]) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -70,7 +70,8 @@ USE_SRCS_FILES = @USE_SRCS_FILES@ USE_SRCS_IPX = @USE_SRCS_IPX@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ +USE_SRCS_UNIX_SOCKETS = @USE_SRCS_UNIX_SOCKETS@ USE_SRCS_WINDOWS = @USE_SRCS_WINDOWS@ WRAPPER = @WRAPPER@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -119,13 +119,10 @@ SRCS_FILES = OFFile.m \ OFINICategory.m \ OFINIFile.m \ OFSettings.m \ OFString+PathAdditions.m -SRCS_IPX = OFIPXSocket.m \ - OFSPXSocket.m \ - OFSPXStreamSocket.m SRCS_PLUGINS = OFPlugin.m SRCS_SOCKETS = OFDNSQuery.m \ OFDNSResolver.m \ OFDNSResourceRecord.m \ OFDNSResponse.m \ @@ -139,11 +136,16 @@ OFSequencedPacketSocket.m \ OFSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ - ${USE_SRCS_IPX} + ${USE_SRCS_IPX} \ + ${USE_SRCS_UNIX_SOCKETS} +SRCS_IPX = OFIPXSocket.m \ + OFSPXSocket.m \ + OFSPXStreamSocket.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m SRCS_THREADS = OFCondition.m \ OFMutex.m \ OFPlainCondition.m \ OFPlainMutex.m \ OFPlainThread.m \ Index: src/OFDatagramSocket.m ================================================================== --- src/OFDatagramSocket.m +++ src/OFDatagramSocket.m @@ -205,10 +205,15 @@ #endif #ifdef OF_HAVE_IPX case AF_IPX: sender->family = OFSocketAddressFamilyIPX; break; +#endif +#ifdef OF_HAVE_UNIX_SOCKETS + case AF_UNIX: + sender->family = OFSocketAddressFamilyUNIX; + break; #endif default: sender->family = OFSocketAddressFamilyUnknown; break; } Index: src/OFSocket.m ================================================================== --- src/OFSocket.m +++ src/OFSocket.m @@ -634,35 +634,22 @@ IPX_NODE_LEN) != 0) return false; return true; case OFSocketAddressFamilyUNIX: - /* - * This is a bit tricky. The only thing that is well-defined is - * the path. So compare the path, and if both don't have a - * path, compare bytes. - */ - - if (address1->length != address2->length) - return false; - pool = objc_autoreleasePoolPush(); path1 = OFSocketAddressUNIXPath(address1); path2 = OFSocketAddressUNIXPath(address2); - if (path1 == nil && path2 == nil) { + if (path1 == nil || path2 == nil) { objc_autoreleasePoolPop(pool); - return (memcmp(&address1->sockaddr.un, - &address2->sockaddr.un, address1->length) == 0); + return false; } - if (path1 == nil || path2 == nil) - ret = false; - else - ret = [path1 isEqual: path2]; + ret = [path1 isEqual: path2]; objc_autoreleasePoolPop(pool); return ret; default: @@ -728,31 +715,18 @@ for (size_t i = 0; i < IPX_NODE_LEN; i++) OFHashAdd(&hash, address->sockaddr.ipx.sipx_node[i]); break; case OFSocketAddressFamilyUNIX:; - /* - * This is a bit tricky. The only thing that is well-defined is - * the path. So hash the path if we have one, otherwise the - * bytes. - */ void *pool = objc_autoreleasePoolPush(); OFString *path = OFSocketAddressUNIXPath(address); - if (path != nil) { - hash = path.hash; - objc_autoreleasePoolPop(pool); - return hash; - } + hash = path.hash; objc_autoreleasePoolPop(pool); - for (socklen_t i = 0; i < address->length; i++) - OFHashAdd(&hash, - ((const char *)&address->sockaddr.un)[i]); - - break; + return hash; default: @throw [OFInvalidArgumentException exception]; } OFHashFinalize(&hash); @@ -932,11 +906,11 @@ } OFString * OFSocketAddressUNIXPath(const OFSocketAddress *_Nonnull address) { - socklen_t maxLength = (socklen_t)sizeof(address->sockaddr.un); + const socklen_t maxLength = (socklen_t)sizeof(address->sockaddr.un); size_t length; if (address->family != OFSocketAddressFamilyUNIX || address->length > maxLength) @throw [OFInvalidArgumentException exception]; @@ -944,10 +918,13 @@ length = sizeof(address->sockaddr.un.sun_path) - (maxLength - address->length); if (length == 0) return nil; + + while (address->sockaddr.un.sun_path[length - 1] == '\0') + length--; return [OFString stringWithCString: address->sockaddr.un.sun_path encoding: [OFLocale encoding] length: length]; } ADDED src/OFUNIXDatagramSocket.h Index: src/OFUNIXDatagramSocket.h ================================================================== --- src/OFUNIXDatagramSocket.h +++ src/OFUNIXDatagramSocket.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFDatagramSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @protocol OFUNIXDatagramSocketDelegate OFUNIXDatagramSocket.h \ + * ObjFW/OFUNIXDatagramSocket.h + * + * @brief A delegate for OFUNIXDatagramSocket. + */ +@protocol OFUNIXDatagramSocketDelegate +@end + +/** + * @class OFUNIXDatagramSocket OFUNIXDatagramSocket.h \ + * ObjFW/OFUNIXDatagramSocket.h + * + * @brief A class which provides methods to create and use UNIX datagram + * sockets. + * + * Addresses are of type @ref OFSocketAddress. You can use + * @ref OFSocketAddressMakeUNIX to create an address or + * @ref OFSocketAddressUNIXPath to get the socket path. + * + * @warning Even though the OFCopying protocol is implemented, it does *not* + * return an independent copy of the socket, but instead retains it. + * This is so that the socket can be used as a key for a dictionary, + * so context can be associated with a socket. Using a socket in more + * than one thread at the same time is not thread-safe, even if copy + * was called to create one "instance" for every thread! + */ +@interface OFUNIXDatagramSocket: OFDatagramSocket +{ + OF_RESERVE_IVARS(OFUNIXDatagramSocket, 4) +} + +/** + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/** + * @brief Bind the socket to the specified path. + * + * @param path The path to bind to + * @return The address on which this socket can be reached + */ +- (OFSocketAddress)bindToPath: (OFString *)path; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFUNIXDatagramSocket.m Index: src/OFUNIXDatagramSocket.m ================================================================== --- src/OFUNIXDatagramSocket.m +++ src/OFUNIXDatagramSocket.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFUNIXDatagramSocket.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" + +@implementation OFUNIXDatagramSocket +@dynamic delegate; + +- (OFSocketAddress)bindToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = OFSocketAddressMakeUNIX(path); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_DGRAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFBindFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPath: path + socket: self + errNo: errNo]; + } + + return address; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -82,10 +82,13 @@ # ifdef OF_HAVE_IPX # import "OFIPXSocket.h" # import "OFSPXSocket.h" # import "OFSPXStreamSocket.h" # endif +# ifdef OF_HAVE_UNIX_SOCKETS +# import "OFUNIXDatagramSocket.h" +# endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" # endif Index: src/exceptions/OFBindFailedException.h ================================================================== --- src/exceptions/OFBindFailedException.h +++ src/exceptions/OFBindFailedException.h @@ -29,16 +29,18 @@ * * @brief An exception indicating that binding a socket failed. */ @interface OFBindFailedException: OFException { - id _socket; /* IP */ OFString *_host; uint16_t _port; /* IPX */ uint8_t _packetType; + /* UNIX socket */ + OFString *_Nullable _path; + id _socket; int _errNo; } /** * @brief The host on which binding failed. @@ -53,10 +55,15 @@ /** * @brief The IPX packet type for which binding failed. */ @property (readonly, nonatomic) uint8_t packetType; +/** + * @brief The path on which binding failed. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *path; + /** * @brief The socket which could not be bound. */ @property (readonly, nonatomic) id socket; @@ -93,10 +100,22 @@ + (instancetype)exceptionWithPort: (uint16_t)port packetType: (uint8_t)packetType socket: (id)socket errNo: (int)errNo; +/** + * @brief Creates a new, autoreleased bind failed exception. + * + * @param path The path on which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return A new, autoreleased bind failed exception + */ ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Initializes an already allocated bind failed exception. * @@ -122,8 +141,19 @@ */ - (instancetype)initWithPort: (uint16_t)port packetType: (uint8_t)packetType socket: (id)socket errNo: (int)errNo; +/** + * @brief Initializes an already allocated bind failed exception. + * + * @param path The path on which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return An initialized bind failed exception + */ +- (instancetype)initWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindFailedException.m ================================================================== --- src/exceptions/OFBindFailedException.m +++ src/exceptions/OFBindFailedException.m @@ -17,11 +17,11 @@ #import "OFBindFailedException.h" #import "OFString.h" @implementation OFBindFailedException -@synthesize host = _host, port = _port, packetType = _packetType; +@synthesize host = _host, port = _port, packetType = _packetType, path = _path; @synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR @@ -46,10 +46,19 @@ return [[[self alloc] initWithPort: port packetType: packetType socket: sock errNo: errNo] autorelease]; } + ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithPath: path + socket: sock + errNo: errNo] autorelease]; +} - (instancetype)init { OF_INVALID_INIT_METHOD } @@ -91,22 +100,45 @@ @throw e; } return self; } + +- (instancetype)initWithPath: (OFString *)path + socket: (id)sock + errNo: (int)errNo +{ + self = [super init]; + + @try { + _path = [path copy]; + _socket = [sock retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - (void)dealloc { [_host release]; + [_path release]; [_socket release]; [super dealloc]; } - (OFString *)description { - if (_host != nil) + if (_path != nil) + return [OFString stringWithFormat: + @"Binding to path %@ failed in socket of type %@: %@", + _path, [_socket class], OFStrError(_errNo)]; + else if (_host != nil) return [OFString stringWithFormat: @"Binding to port %" @PRIu16 @" on host %@ failed in " @"socket of type %@: %@", _port, _host, [_socket class], OFStrError(_errNo)]; else Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -54,23 +54,25 @@ OFSHA1HashTests.m \ OFSHA224HashTests.m \ OFSHA256HashTests.m \ OFSHA384HashTests.m \ OFSHA512HashTests.m -SRCS_IPX = OFIPXSocketTests.m \ - OFSPXSocketTests.m \ - OFSPXStreamSocketTests.m SRCS_PLUGINS = OFPluginTests.m SRCS_SOCKETS = OFDNSResolverTests.m \ ${OF_HTTP_CLIENT_TESTS_M} \ OFHTTPCookieTests.m \ OFHTTPCookieManagerTests.m \ OFKernelEventObserverTests.m \ OFSocketTests.m \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ - ${USE_SRCS_IPX} + ${USE_SRCS_IPX} \ + ${USE_SRCS_UNIX_SOCKETS} +SRCS_IPX = OFIPXSocketTests.m \ + OFSPXSocketTests.m \ + OFSPXStreamSocketTests.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m IOS_USER ?= mobile IOS_TMP ?= /tmp/objfw-test ADDED tests/OFUNIXDatagramSocketTests.m Index: tests/OFUNIXDatagramSocketTests.m ================================================================== --- tests/OFUNIXDatagramSocketTests.m +++ tests/OFUNIXDatagramSocketTests.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *const module = @"OFUNIXDatagramSocket"; + +@implementation TestsAppDelegate (OFUNIXDatagramSocketTests) +- (void)UNIXDatagramSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFString *path; + OFUNIXDatagramSocket *sock; + OFSocketAddress address1, address2; + char buffer[5]; + + path = [[OFSystemInfo temporaryDirectoryPath] + stringByAppendingPathComponent: [[OFUUID UUID] UUIDString]]; + + TEST(@"+[socket]", (sock = [OFUNIXDatagramSocket socket])) + + TEST(@"-[bindToPath:]", R(address1 = [sock bindToPath: path])) + + TEST(@"-[sendBuffer:length:receiver:]", + R([sock sendBuffer: "Hello" length: 5 receiver: &address1])) + + TEST(@"-[receiveIntoBuffer:length:sender:]", + [sock receiveIntoBuffer: buffer length: 5 sender: &address2] == 5 && + memcmp(buffer, "Hello", 5) == 0 && + OFSocketAddressEqual(&address1, &address2) && + OFSocketAddressHash(&address1) == OFSocketAddressHash(&address2)) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -240,10 +240,14 @@ @end @interface TestsAppDelegate (OFUDPSocketTests) - (void)UDPSocketTests; @end + +@interface TestsAppDelegate (OFUNIXDatagramSocketTests) +- (void)UNIXDatagramSocketTests; +@end @interface TestsAppDelegate (OFURLTests) - (void)URLTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -353,10 +353,13 @@ # ifdef OF_HAVE_IPX [self IPXSocketTests]; [self SPXSocketTests]; [self SPXStreamSocketTests]; # endif +# ifdef OF_HAVE_UNIX_SOCKETS + [self UNIXDatagramSocketTests]; +# endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests]; #endif