Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -141,11 +141,12 @@ ${USE_SRCS_IPX} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_IPX = OFIPXSocket.m \ OFSPXSocket.m \ OFSPXStreamSocket.m -SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m \ + OFUNIXStreamSocket.m SRCS_THREADS = OFCondition.m \ OFMutex.m \ OFPlainCondition.m \ OFPlainMutex.m \ OFPlainThread.m \ Index: src/OFStreamSocket.m ================================================================== --- src/OFStreamSocket.m +++ src/OFStreamSocket.m @@ -297,10 +297,15 @@ #endif #ifdef OF_HAVE_IPX case AF_IPX: client->_remoteAddress.family = OFSocketAddressFamilyIPX; break; +#endif +#ifdef OF_HAVE_UNIX_SOCKETS + case AF_UNIX: + client->_remoteAddress.family = OFSocketAddressFamilyUNIX; + break; #endif default: client->_remoteAddress.family = OFSocketAddressFamilyUnknown; break; } Index: src/OFTCPSocket.h ================================================================== --- src/OFTCPSocket.h +++ src/OFTCPSocket.h @@ -144,27 +144,27 @@ * @return The port to use as a SOCKS5 proxy when creating a new socket */ + (uint16_t)SOCKS5Port; /** - * @brief Connect the OFTCPSocket to the specified destination. + * @brief Connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to */ - (void)connectToHost: (OFString *)host port: (uint16_t)port; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to */ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param runLoopMode The run loop mode in which to perform the async connect */ @@ -172,11 +172,11 @@ port: (uint16_t)port runLoopMode: (OFRunLoopMode)runLoopMode; #ifdef OF_HAVE_BLOCKS /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param block The block to execute once the connection has been established */ @@ -183,11 +183,11 @@ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port block: (OFTCPSocketAsyncConnectBlock)block; /** - * @brief Asynchronously connect the OFTCPSocket to the specified destination. + * @brief Asynchronously connects the OFTCPSocket to the specified destination. * * @param host The host to connect to * @param port The port on the host to connect to * @param runLoopMode The run loop mode in which to perform the async connect * @param block The block to execute once the connection has been established @@ -197,11 +197,11 @@ runLoopMode: (OFRunLoopMode)runLoopMode block: (OFTCPSocketAsyncConnectBlock)block; #endif /** - * @brief Bind the socket to the specified host and port. + * @brief Binds the socket to the specified host and port. * * @param host The host to bind to. Use `@"0.0.0.0"` for IPv4 or `@"::"` for * IPv6 to bind to all. * @param port The port to bind to. If the port is 0, an unused port will be * chosen, which can be obtained using the return value. Index: src/OFUNIXDatagramSocket.m ================================================================== --- src/OFUNIXDatagramSocket.m +++ src/OFUNIXDatagramSocket.m @@ -22,10 +22,11 @@ #endif #import "OFUNIXDatagramSocket.h" #import "OFSocket.h" #import "OFSocket+Private.h" +#import "OFString.h" #import "OFAlreadyConnectedException.h" #import "OFBindFailedException.h" @implementation OFUNIXDatagramSocket ADDED src/OFUNIXStreamSocket.h Index: src/OFUNIXStreamSocket.h ================================================================== --- src/OFUNIXStreamSocket.h +++ src/OFUNIXStreamSocket.h @@ -0,0 +1,68 @@ +/* + * 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 "OFStreamSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/** + * @protocol OFUNIXStreamSocketDelegate OFUNIXStreamSocket.h \ + * ObjFW/OFUNIXStreamSocket.h + * + * A delegate for OFUNIXStreamSocket. + */ +@protocol OFUNIXStreamSocketDelegate +@end + +/** + * @class OFUNIXStreamSocket OFUNIXStreamSocket.h ObjFW/OFUNIXStreamSocket.h + * + * @brief A class which provides methods to create and use UNIX stream sockets. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFUNIXStreamSocket: OFStreamSocket +{ + OF_RESERVE_IVARS(OFUNIXStreamSocket, 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 Connects the OFUNIXStreamSocket to the specified destination. + * + * @param path The path to connect to + */ +- (void)connectToPath: (OFString *)path; + +/** + * @brief Binds the socket to the specified host and port. + * + * @param path The path to bind to + */ +- (void)bindToPath: (OFString *)path; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFUNIXStreamSocket.m Index: src/OFUNIXStreamSocket.m ================================================================== --- src/OFUNIXStreamSocket.m +++ src/OFUNIXStreamSocket.m @@ -0,0 +1,111 @@ +/* + * 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 "OFUNIXStreamSocket.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" +#import "OFString.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" + +@implementation OFUNIXStreamSocket +@dynamic delegate; + +- (void)connectToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && 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_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFConnectionFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (connect(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFConnectionFailedException exceptionWithPath: path + socket: self + errNo: errNo]; + } +} + +- (void)bindToPath: (OFString *)path +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && 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_STREAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFBindFailedException + exceptionWithPath: path + socket: self + errNo: OFSocketErrNo()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && 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]; + } +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -84,10 +84,11 @@ # import "OFSPXSocket.h" # import "OFSPXStreamSocket.h" # endif # ifdef OF_HAVE_UNIX_SOCKETS # import "OFUNIXDatagramSocket.h" +# import "OFUNIXStreamSocket.h" # endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" Index: src/exceptions/OFConnectionFailedException.h ================================================================== --- src/exceptions/OFConnectionFailedException.h +++ src/exceptions/OFConnectionFailedException.h @@ -29,23 +29,19 @@ * * @brief An exception indicating that a connection could not be established. */ @interface OFConnectionFailedException: OFException { - id _socket; OFString *_host; uint16_t _port; unsigned char _node[IPX_NODE_LEN]; uint32_t _network; + OFString *_Nullable _path; + id _socket; int _errNo; } -/** - * @brief The socket which could not connect. - */ -@property (readonly, nonatomic) id socket; - /** * @brief The host to which the connection failed. */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *host; @@ -62,10 +58,20 @@ /** * @brief The IPX network of the node to which the connection failed. */ @property (readonly, nonatomic) uint32_t network; +/** + * @brief The path to which the connection failed. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *path; + +/** + * @brief The socket which could not connect. + */ +@property (readonly, nonatomic) id socket; + /** * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -99,10 +105,22 @@ network: (uint32_t)network port: (uint16_t)port socket: (id)socket errNo: (int)errNo; +/** + * @brief Creates a new, autoreleased connection failed exception. + * + * @param path The path to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return A new, autoreleased connection failed exception + */ ++ (instancetype)exceptionWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /** * @brief Initializes an already allocated connection failed exception. * @@ -130,8 +148,20 @@ - (instancetype)initWithNode: (unsigned char [_Nullable IPX_NODE_LEN])node network: (uint32_t)network port: (uint16_t)port socket: (id)socket errNo: (int)errNo; + +/** + * @brief Initializes an already allocated connection failed exception. + * + * @param path The path to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return An initialized connection failed exception + */ +- (instancetype)initWithPath: (OFString *)path + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFConnectionFailedException.m ================================================================== --- src/exceptions/OFConnectionFailedException.m +++ src/exceptions/OFConnectionFailedException.m @@ -17,12 +17,12 @@ #import "OFConnectionFailedException.h" #import "OFString.h" @implementation OFConnectionFailedException -@synthesize host = _host, port = _port, network = _network, socket = _socket; -@synthesize errNo = _errNo; +@synthesize host = _host, port = _port, network = _network, path = _path; +@synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } @@ -48,10 +48,19 @@ network: network port: port 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 } @@ -95,14 +104,33 @@ @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]; } @@ -111,11 +139,16 @@ return _node; } - (OFString *)description { - if (_host != nil) + if (_path != nil) + return [OFString stringWithFormat: + @"A connection to %@ could not be established in socket of " + @"type %@: %@", + _path, [_socket class], OFStrError(_errNo)]; + else if (_host != nil) return [OFString stringWithFormat: @"A connection to %@ on port %" @PRIu16 @" could not be " @"established in socket of type %@: %@", _host, _port, [_socket class], OFStrError(_errNo)]; else if (memcmp(_node, "\0\0\0\0\0", IPX_NODE_LEN) == 0) Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -68,11 +68,12 @@ ${USE_SRCS_IPX} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_IPX = OFIPXSocketTests.m \ OFSPXSocketTests.m \ OFSPXStreamSocketTests.m -SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m +SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m \ + OFUNIXStreamSocketTests.m SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m IOS_USER ?= mobile IOS_TMP ?= /tmp/objfw-test Index: tests/OFSPXSocketTests.m ================================================================== --- tests/OFSPXSocketTests.m +++ tests/OFSPXSocketTests.m @@ -70,11 +70,11 @@ @implementation TestsAppDelegate (OFSPXSocketTests) - (void)SPXSocketTests { void *pool = objc_autoreleasePoolPush(); - OFSPXSocket *sockClient, *sockServer, *sockAccepted;; + OFSPXSocket *sockClient, *sockServer, *sockAccepted; OFSocketAddress address1; const OFSocketAddress *address2; unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; uint32_t network; uint16_t port; Index: tests/OFSPXStreamSocketTests.m ================================================================== --- tests/OFSPXStreamSocketTests.m +++ tests/OFSPXStreamSocketTests.m @@ -70,11 +70,11 @@ @implementation TestsAppDelegate (OFSPXStreamSocketTests) - (void)SPXStreamSocketTests { void *pool = objc_autoreleasePoolPush(); - OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted;; + OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted; OFSocketAddress address1; const OFSocketAddress *address2; unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; uint32_t network; uint16_t port; ADDED tests/OFUNIXStreamSocketTests.m Index: tests/OFUNIXStreamSocketTests.m ================================================================== --- tests/OFUNIXStreamSocketTests.m +++ tests/OFUNIXStreamSocketTests.m @@ -0,0 +1,87 @@ +/* + * 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 = @"OFUNIXStreamSocket"; + +@implementation TestsAppDelegate (OFUNIXStreamSocketTests) +- (void)UNIXStreamSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFString *path; + OFUNIXStreamSocket *sockClient, *sockServer, *sockAccepted; + char buffer[5]; + +#ifdef OF_HAVE_FILES + path = [[OFSystemInfo temporaryDirectoryPath] + stringByAppendingPathComponent: [[OFUUID UUID] UUIDString]]; +#else + /* + * We can have sockets, including UNIX sockets, while file support is + * disabled. + */ + path = [OFString stringWithFormat: @"/tmp/%@", + [[OFUUID UUID] UUIDString]]; +#endif + + TEST(@"+[socket]", (sockClient = [OFUNIXStreamSocket socket]) && + (sockServer = [OFUNIXStreamSocket socket])) + + @try { + TEST(@"-[bindToPath:]", R([sockServer bindToPath: path])) + } @catch (OFBindFailedException *e) { + if (e.errNo == EAFNOSUPPORT) { + [OFStdOut setForegroundColor: [OFColor lime]]; + [OFStdOut writeLine: + @"\r[OFUNIXStreamSocket] -[bindToPath:]: " + @"UNIX stream sockets unsupported, skipping tests"]; + + objc_autoreleasePoolPop(pool); + return; + } else + @throw e; + } + + @try { + TEST(@"-[listen]", R([sockServer listen])) + + TEST(@"-[connectToPath:]", + R([sockClient connectToPath: path])) + + TEST(@"-[accept]", (sockAccepted = [sockServer accept])) + + TEST(@"-[writeBuffer:length:]", + R([sockAccepted writeBuffer: "Hello" length: 5])) + + TEST(@"-[receiveIntoBuffer:length:]", + [sockClient readIntoBuffer: buffer length: 5] == 5 && + memcmp(buffer, "Hello", 5) == 0) + + TEST(@"-[remoteAddress]", + OFSocketAddressUNIXPath(sockAccepted.remoteAddress) == nil) + } @finally { +#ifdef OF_HAVE_FILES + [[OFFileManager defaultManager] removeItemAtPath: path]; +#endif + } + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -244,10 +244,14 @@ @end @interface TestsAppDelegate (OFUNIXDatagramSocketTests) - (void)UNIXDatagramSocketTests; @end + +@interface TestsAppDelegate (OFUNIXStreamSocketTests) +- (void)UNIXStreamSocketTests; +@end @interface TestsAppDelegate (OFURLTests) - (void)URLTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -355,10 +355,11 @@ [self SPXSocketTests]; [self SPXStreamSocketTests]; # endif # ifdef OF_HAVE_UNIX_SOCKETS [self UNIXDatagramSocketTests]; + [self UNIXStreamSocketTests]; # endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests];