Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1450,14 +1450,11 @@ #ifdef AF_IPX egrep_cpp_yes #endif ], [ AC_DEFINE(OF_HAVE_IPX, 1, [Whether we have IPX/SPX]) - AC_SUBST(OF_IPX_SOCKET_M, OFIPXSocket.m) - AC_SUBST(OF_IPX_SOCKET_TESTS_M, OFIPXSocketTests.m) - AC_SUBST(OF_SPX_SOCKET_M, OFSPXSocket.m) - AC_SUBST(OF_SPX_SOCKET_TESTS_M, OFSPXSocketTests.m) + AC_SUBST(USE_SRCS_IPX, '${SRCS_IPX}') ]) ]) AC_CHECK_FUNCS(paccept accept4, break) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -58,18 +58,14 @@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ OF_BLOCK_TESTS_M = @OF_BLOCK_TESTS_M@ OF_EPOLL_KERNEL_EVENT_OBSERVER_M = @OF_EPOLL_KERNEL_EVENT_OBSERVER_M@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ -OF_IPX_SOCKET_M = @OF_IPX_SOCKET_M@ -OF_IPX_SOCKET_TESTS_M = @OF_IPX_SOCKET_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ OF_PROCESS_M = @OF_PROCESS_M@ OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ -OF_SPX_SOCKET_M = @OF_SPX_SOCKET_M@ -OF_SPX_SOCKET_TESTS_M = @OF_SPX_SOCKET_TESTS_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_FRAMEWORK_LIBS = @RUNTIME_FRAMEWORK_LIBS@ RUNTIME_LIBS = @RUNTIME_LIBS@ @@ -82,10 +78,11 @@ TESTS_LIBS = @TESTS_LIBS@ TESTS_STATIC_LIB = @TESTS_STATIC_LIB@ UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ 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_WINDOWS = @USE_SRCS_WINDOWS@ WRAPPER = @WRAPPER@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -122,19 +122,23 @@ once.m \ pbkdf2.m \ scrypt.m \ ${UNICODE_M} \ ${USE_SRCS_FILES} \ + ${USE_SRCS_IPX} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} \ ${USE_SRCS_WINDOWS} 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 \ @@ -143,12 +147,10 @@ OFHTTPCookie.m \ OFHTTPCookieManager.m \ OFHTTPRequest.m \ OFHTTPResponse.m \ OFHTTPServer.m \ - ${OF_IPX_SOCKET_M} \ - ${OF_SPX_SOCKET_M} \ OFSequencedPacketSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ socket.m Index: src/OFSPXSocket.h ================================================================== --- src/OFSPXSocket.h +++ src/OFSPXSocket.h @@ -63,10 +63,13 @@ /*! * @class OFSPXSocket OFSPXSocket.h ObjFW/OFSPXSocket.h * * @brief A class which provides methods to create and use SPX sockets. + * + * @note If you want to use SPX in streaming mode instead of in message mode, + * use @ref OFSPXStreamSocket instead. * * 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 OFSPXSocket: OFSequencedPacketSocket ADDED src/OFSPXStreamSocket.h Index: src/OFSPXStreamSocket.h ================================================================== --- src/OFSPXStreamSocket.h +++ src/OFSPXStreamSocket.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * 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" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFSPXStreamSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_spx_stream_socket_async_connect_block_t)( + id _Nullable exception); +#endif + +/*! + * @protocol OFSPXStreamSocketDelegate OFSPXStreamSocket.h \ + * ObjFW/OFSPXStreamSocket.h + * + * A delegate for OFSPXStreamSocket. + */ +@protocol OFSPXStreamSocketDelegate +@optional +/*! + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param node The node the socket connected to + * @param network The network of the node the socket connected to + * @param port The port of the node to which the socket connected + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSPXStreamSocket *)socket + didConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (nullable id)exception; +@end + +/*! + * @class OFSPXStreamSocket OFSPXStreamSocket.h ObjFW/OFSPXStreamSocket.h + * + * @brief A class which provides methods to create and use SPX stream sockets. + * + * @note If you want to use SPX in message mode instead of in streaming mode, + * use @ref OFSPXSocket instead. + * + * 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 OFSPXStreamSocket: OFStreamSocket +{ + OF_RESERVE_IVARS(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 Connect the OFSPXStreamSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param runLoopMode The run loop mode in which to perform the async connect + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_stream_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node 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 + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_stream_socket_async_connect_block_t)block; +#endif + +/*! + * @brief Bind the socket to the specified network, node and port with the + * specified packet type. + * + * @param port The port (sometimes called socket number) to bind to. 0 means to + * pick one and return it. + * @return The address on which this socket can be reached + */ +- (of_socket_address_t)bindToPort: (uint16_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSPXStreamSocket.m Index: src/OFSPXStreamSocket.m ================================================================== --- src/OFSPXStreamSocket.m +++ src/OFSPXStreamSocket.m @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * 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 "OFSPXStreamSocket.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" +#import "OFNotOpenException.h" + +#import "socket.h" +#import "socket_helpers.h" + +#ifndef NSPROTO_SPX +# define NSPROTO_SPX 0 +#endif + +#define SPX_PACKET_TYPE 5 + +@interface OFSPXStreamSocket () +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface OFSPXStreamSocketAsyncConnectDelegate: OFObject + +{ + OFSPXStreamSocket *_socket; + unsigned char _node[IPX_NODE_LEN]; + uint32_t _network; + uint16_t _port; +#ifdef OF_HAVE_BLOCKS + of_spx_stream_socket_async_connect_block_t _block; +#endif +} + +- (instancetype)initWithSocket: (OFSPXStreamSocket *)socket + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_stream_socket_async_connect_block_t) + block +#endif +; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@end + +@implementation OFSPXStreamSocketAsyncConnectDelegate +- (instancetype)initWithSocket: (OFSPXStreamSocket *)sock + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_stream_socket_async_connect_block_t) + block +#endif +{ + self = [super init]; + + @try { + _socket = [sock retain]; + memcpy(_node, node, IPX_NODE_LEN); + _network = network; + _port = port; +#ifdef OF_HAVE_BLOCKS + _block = [block copy]; +#endif + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_socket release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + + [super dealloc]; +} + +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = + of_socket_address_ipx(_node, _network, _port); + id exception = nil; + int errNo; + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + goto inform_delegate; + } + + _socket.blocking = false; + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } + + [_socket of_closeSocket]; + + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + } + +inform_delegate: + [self performSelector: @selector(of_socketDidConnect:exception:) + withObject: _socket + withObject: exception + afterDelay: 0]; +} + +- (void)of_socketDidConnect: (id)sock + exception: (id)exception +{ + id delegate = + ((OFSPXStreamSocket *)sock).delegate; + + if (exception == nil) + ((OFSPXStreamSocket *)sock).blocking = true; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(exception); + else { +#endif + if ([delegate respondsToSelector: + @selector(socket:didConnectToNode:network:port:exception:)]) + [delegate socket: _socket + didConnectToNode: _node + network: _network + port: _port + exception: exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (id)of_connectionFailedExceptionForErrNo: (int)errNo +{ + return [OFConnectionFailedException exceptionWithNode: _node + network: _network + port: _port + socket: _socket + errNo: errNo]; +} +@end + +@implementation OFSPXStreamSocket +@dynamic delegate; + +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_socket = socket(address->sockaddr.ipx.sipx_family, + SOCK_SEQPACKET | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) { + *errNo = of_socket_errno(); + return false; + } + +#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 + + return true; +} + +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, &address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + of_socket_address_t address = + of_socket_address_ipx(node, network, port); + int errNo; + + if (![self of_createSocketForAddress: &address + errNo: &errNo]) + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + + if (![self of_connectSocketToAddress: &address + errNo: &errNo]) { + [self of_closeSocket]; + + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + } +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXStreamSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port +#ifdef OF_HAVE_BLOCKS + block: NULL +#endif + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_stream_socket_async_connect_block_t)block +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_stream_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXStreamSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port + block: block + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (of_socket_address_t)bindToPort: (uint16_t)port +{ + const unsigned char zeroNode[IPX_NODE_LEN] = { 0 }; + of_socket_address_t address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = of_socket_address_ipx(zeroNode, 0, port); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: of_socket_errno()]; + + _blocking = 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 = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + address.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + address.length = (socklen_t)sizeof(address.sockaddr); + + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family != AF_IPX) { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -79,10 +79,11 @@ # import "OFDNSResponse.h" # import "OFDNSResolver.h" # ifdef OF_HAVE_IPX # import "OFIPXSocket.h" # import "OFSPXSocket.h" +# import "OFSPXStreamSocket.h" # endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -38,10 +38,11 @@ PBKDF2Tests.m \ RuntimeTests.m \ ScryptTests.m \ TestsAppDelegate.m \ ${USE_SRCS_FILES} \ + ${USE_SRCS_IPX} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} \ ${USE_SRCS_WINDOWS} SRCS_FILES = OFHMACTests.m \ @@ -52,18 +53,19 @@ 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 \ - ${OF_IPX_SOCKET_TESTS_M} \ OFKernelEventObserverTests.m \ - ${OF_SPX_SOCKET_TESTS_M} \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ SocketTests.m SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m ADDED tests/OFSPXStreamSocketTests.m Index: tests/OFSPXStreamSocketTests.m ================================================================== --- tests/OFSPXStreamSocketTests.m +++ tests/OFSPXStreamSocketTests.m @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * 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 *module = @"OFSPXStreamSocket"; + +@interface SPXStreamSocketDelegate: OFObject +{ +@public + OFStreamSocket *_expectedServerSocket; + OFSPXStreamSocket *_expectedClientSocket; + unsigned char _expectedNode[IPX_NODE_LEN]; + uint32_t _expectedNetwork; + uint16_t _expectedPort; + bool _accepted; + bool _connected; +} +@end + +@implementation SPXStreamSocketDelegate +- (bool)socket: (OFStreamSocket *)sock + didAcceptSocket: (OFStreamSocket *)accepted + exception: (id)exception +{ + OF_ENSURE(!_accepted); + + _accepted = (sock == _expectedServerSocket && accepted != nil && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; + + return false; +} + +- (void)socket: (OFSPXStreamSocket *)sock + didConnectToNode: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (id)exception +{ + OF_ENSURE(!_connected); + + _connected = (sock == _expectedClientSocket && + memcmp(node, _expectedNode, IPX_NODE_LEN) == 0 && + network == _expectedNetwork && port == _expectedPort && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; +} +@end + +@implementation TestsAppDelegate (OFSPXStreamSocketTests) +- (void)SPXStreamSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted;; + of_socket_address_t address1; + const of_socket_address_t *address2; + unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; + uint32_t network; + uint16_t port; + char buffer[5]; + SPXStreamSocketDelegate *delegate; + + TEST(@"+[socket]", (sockClient = [OFSPXStreamSocket socket]) && + (sockServer = [OFSPXStreamSocket socket])) + + @try { + TEST(@"-[bindToPort:]", + R(address1 = [sockServer bindToPort: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EAFNOSUPPORT: + [self outputString: @"[OFSPXStreamSocket] " + @"-[bindToPort:]: " + @"IPX unsupported, skipping tests\n" + inColor: GREEN]; + break; + case ESOCKTNOSUPPORT: + [self outputString: @"[OFSPXStreamSocket] " + @"-[bindToPort:]: " + @"SPX unsupported, skipping tests\n" + inColor: GREEN]; + break; + case EADDRNOTAVAIL: + [self outputString: @"[OFSPXStreamSocket] " + @"-[bindToPort:]: " + @"IPX not configured, skipping " + @"tests\n" + inColor: GREEN]; + break; + default: + @throw e; + } + + objc_autoreleasePoolPop(pool); + return; + } + + of_socket_address_get_ipx_node(&address1, node); + network = of_socket_address_get_ipx_network(&address1); + port = of_socket_address_get_port(&address1); + + TEST(@"-[listen]", R([sockServer listen])) + + TEST(@"-[connectToNode:network:port:]", + R([sockClient connectToNode: node + network: network + port: port])) + + TEST(@"-[accept]", (sockAccepted = [sockServer accept])) + + /* Test reassembly (this would not work with OFSPXSocket) */ + TEST(@"-[writeBuffer:length:]", + R([sockAccepted writeBuffer: "Hello" + length: 5])) + + TEST(@"-[readIntoBuffer:length:]", + [sockClient readIntoBuffer: buffer + length: 2] == 2 && + memcmp(buffer, "He", 2) == 0 && + [sockClient readIntoBuffer: buffer + length: 3] == 3 && + memcmp(buffer, "llo", 3) == 0) + + TEST(@"-[remoteAddress]", + (address2 = sockAccepted.remoteAddress) && + R(of_socket_address_get_ipx_node(address2, node2)) && + memcmp(node, node2, IPX_NODE_LEN) == 0 && + of_socket_address_get_ipx_network(address2) == network) + + delegate = [[[SPXStreamSocketDelegate alloc] init] autorelease]; + + sockServer = [OFSPXStreamSocket socket]; + delegate->_expectedServerSocket = sockServer; + sockServer.delegate = delegate; + + sockClient = [OFSPXStreamSocket socket]; + delegate->_expectedClientSocket = sockClient; + sockClient.delegate = delegate; + + address1 = [sockServer bindToPort: 0]; + [sockServer listen]; + [sockServer asyncAccept]; + + of_socket_address_get_ipx_node(&address1, node); + memcpy(delegate->_expectedNode, node, IPX_NODE_LEN); + delegate->_expectedNetwork = network = + of_socket_address_get_ipx_network(&address1); + delegate->_expectedPort = port = of_socket_address_get_port(&address1); + + [sockClient asyncConnectToNode: node + network: network + port: port]; + + [[OFRunLoop mainRunLoop] runUntilDate: + [OFDate dateWithTimeIntervalSinceNow: 2]]; + + TEST(@"-[asyncAccept] & -[asyncConnectToNode:network:port:]", + delegate->_accepted && delegate->_connected) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -231,10 +231,14 @@ @end @interface TestsAppDelegate (OFSPXSocketTests) - (void)SPXSocketTests; @end + +@interface TestsAppDelegate (OFSPXStreamSocketTests) +- (void)SPXStreamSocketTests; +@end @interface TestsAppDelegate (OFSystemInfoTests) - (void)systemInfoTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -416,10 +416,11 @@ [self TCPSocketTests]; [self UDPSocketTests]; # ifdef OF_HAVE_IPX [self IPXSocketTests]; [self SPXSocketTests]; + [self SPXStreamSocketTests]; # endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests];