Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1328,10 +1328,14 @@ AC_CHECK_HEADER(netinet/tcp.h, [ AC_DEFINE(OF_HAVE_NETINET_TCP_H, 1, [Whether we have netinet/tcp.h]) ]) AC_CHECK_HEADERS([arpa/inet.h netdb.h]) + AC_CHECK_HEADER(netipx/ipx.h, [ + AC_DEFINE(OF_HAVE_NETIPX_IPX_H, 1, + [Whether we have netipx/ipx.h]) + ]) AC_CHECK_MEMBER([struct sockaddr_in6.sin6_addr], [ AC_EGREP_CPP(egrep_cpp_yes, [ #ifdef _WIN32 typedef int BOOL; @@ -1377,10 +1381,83 @@ # endif # include # include #endif ]) + + AC_CHECK_MEMBER(struct sockaddr_ipx.sipx_network, [], [ + AC_CHECK_MEMBER(struct sockaddr_ipx.sa_netnum, [], [], [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_NETIPX_IPX_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + ]) + ], [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_NETIPX_IPX_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + ]) + AS_IF([test x"$ac_cv_member_struct_sockaddr_ipx_sipx_network" = x"yes" \ + -o x"$ac_cv_member_struct_sockaddr_ipx_sa_netnum" = x"yes"], [ + AC_EGREP_CPP(egrep_cpp_yes, [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_SYS_SOCKET_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + + #ifdef AF_IPX + egrep_cpp_yes + #endif + ], [ + AC_DEFINE(OF_HAVE_IPX, 1, [Whether we have IPX/SPX]) + AC_SUBST(OFIPXSOCKET_M, OFIPXSocket.m) + AC_SUBST(OFIPXSOCKETTESTS_M, OFIPXSocketTests.m) + ]) + ]) AC_CHECK_FUNCS(paccept accept4, break) AC_CHECK_FUNCS(kqueue1 kqueue, [ AC_DEFINE(HAVE_KQUEUE, 1, [Whether we have kqueue]) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -58,10 +58,12 @@ OFDNS = @OFDNS@ OFEPOLLKERNELEVENTOBSERVER_M = @OFEPOLLKERNELEVENTOBSERVER_M@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ OFHTTPCLIENTTESTS_M = @OFHTTPCLIENTTESTS_M@ +OFIPXSOCKETTESTS_M = @OFIPXSOCKETTESTS_M@ +OFIPXSOCKET_M = @OFIPXSOCKET_M@ OFKQUEUEKERNELEVENTOBSERVER_M = @OFKQUEUEKERNELEVENTOBSERVER_M@ OFPOLLKERNELEVENTOBSERVER_M = @OFPOLLKERNELEVENTOBSERVER_M@ OFPROCESS_M = @OFPROCESS_M@ OFSELECTKERNELEVENTOBSERVER_M = @OFSELECTKERNELEVENTOBSERVER_M@ OFSTDIOSTREAM_WIN32CONSOLE_M = @OFSTDIOSTREAM_WIN32CONSOLE_M@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -143,10 +143,11 @@ OFHTTPCookie.m \ OFHTTPCookieManager.m \ OFHTTPRequest.m \ OFHTTPResponse.m \ OFHTTPServer.m \ + ${OFIPXSOCKET_M} \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ socket.m SRCS_THREADS = OFCondition.m \ Index: src/OFDNSResolver.m ================================================================== --- src/OFDNSResolver.m +++ src/OFDNSResolver.m @@ -1049,11 +1049,11 @@ exception: exception]; return false; } -- (bool)socket: (OFUDPSocket *)sock +- (bool)socket: (OFDatagramSocket *)sock didReceiveIntoBuffer: (void *)buffer length: (size_t)length sender: (const of_socket_address_t *)sender exception: (id)exception { Index: src/OFDatagramSocket.h ================================================================== --- src/OFDatagramSocket.h +++ src/OFDatagramSocket.h @@ -23,10 +23,11 @@ OF_ASSUME_NONNULL_BEGIN /*! @file */ +@class OFData; @class OFDatagramSocket; #ifdef OF_HAVE_BLOCKS /*! * @brief A block which is called when a packet has been received. @@ -82,11 +83,11 @@ length: (size_t)length sender: (const of_socket_address_t *_Nonnull)sender exception: (nullable id)exception; /*! - * @brief This which is called when a packet has been sent. + * @brief This method is called when a packet has been sent. * * @param socket The datagram socket which sent a packet * @param data The data which was sent * @param receiver The receiver for the packet * @param exception An exception that occurred while sending, or nil on success Index: src/OFDatagramSocket.m ================================================================== --- src/OFDatagramSocket.m +++ src/OFDatagramSocket.m @@ -166,10 +166,15 @@ break; #ifdef OF_HAVE_IPV6 case AF_INET6: sender->family = OF_SOCKET_ADDRESS_FAMILY_IPV6; break; +#endif +#ifdef OF_HAVE_IPX + case AF_IPX: + sender->family = OF_SOCKET_ADDRESS_FAMILY_IPX; + break; #endif default: sender->family = OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; break; } ADDED src/OFIPXSocket.h Index: src/OFIPXSocket.h ================================================================== --- src/OFIPXSocket.h +++ src/OFIPXSocket.h @@ -0,0 +1,79 @@ +/* + * 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 "OFDatagramSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/*! + * @protocol OFIPXSocketDelegate OFIPXSocket.h ObjFW/OFIPXSocket.h + * + * @brief A delegate for OFIPXSocket. + */ +@protocol OFIPXSocketDelegate +@end + +/*! + * @class OFIPXSocket OFIPXSocket.h ObjFW/OFIPXSocket.h + * + * @brief A class which provides methods to create and use IPX sockets. + * + * Addresses are of type @ref of_socket_address_t. You can use + * @ref of_socket_address_ipx to create an address or + * @ref of_socket_address_ipx_get to get the IPX network, node and port + * (somtimes also called socket number). + * + * @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 OFIPXSocket: OFDatagramSocket +{ +#ifndef OF_WINDOWS + uint8_t _packetType; +#endif + 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 outstanding. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Binds 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. + * @param packetType The packet type to use on the socket + * @return The address on which this socket can be reached + */ +- (of_socket_address_t)bindToPort: (uint16_t)port + packetType: (uint8_t)packetType; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFIPXSocket.m Index: src/OFIPXSocket.m ================================================================== --- src/OFIPXSocket.m +++ src/OFIPXSocket.m @@ -0,0 +1,133 @@ +/* + * 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 + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFIPXSocket.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +@implementation OFIPXSocket +@dynamic delegate; + +- (of_socket_address_t)bindToPort: (uint16_t)port + packetType: (uint8_t)packetType +{ + const unsigned char zeroNode[IPX_NODE_LEN] = { 0 }; + of_socket_address_t address; + int protocol = 0; +#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(0, zeroNode, port); + +#ifdef OF_WINDOWS + protocol = NSPROTO_IPX + packetType; +#else + _packetType = address.sockaddr.ipx.sipx_type = packetType; +#endif + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_DGRAM | SOCK_CLOEXEC, protocol)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithPort: port + packetType: packetType + 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: packetType + 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: packetType + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family != AF_IPX) { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: packetType + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} + +#ifndef OF_WINDOWS +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + receiver: (const of_socket_address_t *)receiver +{ + of_socket_address_t fixedReceiver; + + memcpy(&fixedReceiver, receiver, sizeof(fixedReceiver)); + + /* If it's not IPX, no fix-up needed - it will fail anyway. */ + if (fixedReceiver.family == OF_SOCKET_ADDRESS_FAMILY_IPX) + fixedReceiver.sockaddr.ipx.sipx_type = _packetType; + + [super sendBuffer: buffer + length: length + receiver: &fixedReceiver]; +} +#endif +@end Index: src/OFUDPSocket.h ================================================================== --- src/OFUDPSocket.h +++ src/OFUDPSocket.h @@ -51,10 +51,11 @@ @interface OFUDPSocket: OFDatagramSocket { #ifdef OF_WII uint16_t _port; #endif + OF_RESERVE_IVARS(4) } /*! * @brief The delegate for asynchronous operations on the socket. * Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -76,10 +76,13 @@ # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" +# ifdef OF_HAVE_IPX +# import "OFIPXSocket.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 @@ -18,10 +18,12 @@ #import "OFException.h" #ifndef OF_HAVE_SOCKETS # error No sockets available! #endif + +#import "socket.h" OF_ASSUME_NONNULL_BEGIN /*! * @class OFBindFailedException \ @@ -30,12 +32,15 @@ * @brief An exception indicating that binding a socket failed. */ @interface OFBindFailedException: OFException { id _socket; + /* IP */ OFString *_host; uint16_t _port; + /* IPX */ + uint8_t _packetType; int _errNo; } /*! * @brief The host on which binding failed. @@ -45,10 +50,15 @@ /*! * @brief The port on which binding failed. */ @property (readonly, nonatomic) uint16_t port; +/*! + * @brief The IPX packet type for which binding failed. + */ +@property (readonly, nonatomic) uint8_t packetType; + /*! * @brief The socket which could not be bound. */ @property (readonly, nonatomic) id socket; @@ -71,10 +81,24 @@ + (instancetype)exceptionWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo; +/*! + * @brief Creates a new, autoreleased bind failed exception. + * + * @param port The IPX port to which binding failed + * @param packetType The IPX packet type for 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)exceptionWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated bind failed exception. * @@ -85,9 +109,23 @@ * @return An initialized bind failed exception */ - (instancetype)initWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket - errNo: (int)errNo OF_DESIGNATED_INITIALIZER; + errNo: (int)errNo; + +/*! + * @brief Initializes an already allocated bind failed exception. + * + * @param port The IPX port to which binding failed + * @param packetType The IPX packet type for 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)initWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindFailedException.m ================================================================== --- src/exceptions/OFBindFailedException.m +++ src/exceptions/OFBindFailedException.m @@ -19,11 +19,12 @@ #import "OFBindFailedException.h" #import "OFString.h" @implementation OFBindFailedException -@synthesize host = _host, port = _port, socket = _socket, errNo = _errNo; +@synthesize host = _host, port = _port, packetType = _packetType; +@synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } @@ -33,10 +34,21 @@ socket: (id)socket errNo: (int)errNo { return [[[self alloc] initWithHost: host port: port + socket: socket + errNo: errNo] autorelease]; +} + ++ (instancetype)exceptionWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo +{ + return [[[self alloc] initWithPort: port + packetType: packetType socket: socket errNo: errNo] autorelease]; } - (instancetype)init @@ -61,10 +73,30 @@ @throw e; } return self; } + +- (instancetype)initWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo +{ + self = [super init]; + + @try { + _port = port; + _packetType = packetType; + _socket = [socket retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} - (void)dealloc { [_host release]; [_socket release]; @@ -72,11 +104,17 @@ [super dealloc]; } - (OFString *)description { - return [OFString stringWithFormat: - @"Binding to port %" @PRIu16 @" on host %@ failed in socket of " - @"type %@: %@", - _port, _host, [_socket class], of_strerror(_errNo)]; + if (_host != nil) + return [OFString stringWithFormat: + @"Binding to port %" @PRIu16 @" on host %@ failed in " + @"socket of type %@: %@", + _port, _host, [_socket class], of_strerror(_errNo)]; + else + return [OFString stringWithFormat: + @"Binding to port %" @PRIx16 @" for packet type %" @PRIx8 + @" failed in socket of type %@: %@", + _port, _packetType, [_socket class], of_strerror(_errNo)]; } @end Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -13,14 +13,16 @@ #undef OF_HAVE_CHMOD #undef OF_HAVE_CHOWN #undef OF_HAVE_FILES #undef OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR #undef OF_HAVE_IPV6 +#undef OF_HAVE_IPX #undef OF_HAVE_LIMITS_H #undef OF_HAVE_LINK #undef OF_HAVE_MAX_ALIGN_T #undef OF_HAVE_NETINET_IN_H +#undef OF_HAVE_NETIPX_IPX_H #undef OF_HAVE_OSATOMIC #undef OF_HAVE_OSATOMIC_64 #undef OF_HAVE_PIPE #undef OF_HAVE_PLEDGE #undef OF_HAVE_PLUGINS Index: src/socket.h ================================================================== --- src/socket.h +++ src/socket.h @@ -32,16 +32,22 @@ # include #endif #ifdef OF_HAVE_NETINET_TCP_H # include #endif +#ifdef OF_HAVE_NETIPX_IPX_H +# include +#endif #include "platform.h" #ifdef OF_WINDOWS # include # include +# ifdef OF_HAVE_IPX +# include +# endif #endif /*! @file */ #ifdef OF_WII @@ -87,10 +93,12 @@ OF_SOCKET_ADDRESS_FAMILY_UNKNOWN, /** IPv4 */ OF_SOCKET_ADDRESS_FAMILY_IPV4, /** IPv6 */ OF_SOCKET_ADDRESS_FAMILY_IPV6, + /** IPX */ + OF_SOCKET_ADDRESS_FAMILY_IPX, /** Any address family */ OF_SOCKET_ADDRESS_FAMILY_ANY = 255 } of_socket_address_family_t; #ifndef OF_HAVE_IPV6 @@ -102,10 +110,28 @@ uint8_t s6_addr[16]; } sin6_addr; uint32_t sin6_scope_id; }; #endif + +#ifndef OF_HAVE_IPX +# define IPX_NODE_LEN 6 +struct sockaddr_ipx { + sa_family_t sipx_family; + uint32_t sipx_network; + unsigned char sipx_node[IPX_NODE_LEN]; + uint16_t sipx_port; + uint8_t sipx_type; +}; +#endif +#ifdef OF_WINDOWS +# define IPX_NODE_LEN 6 +# define sipx_family sa_family +# define sipx_network sa_netnum +# define sipx_node sa_nodenum +# define sipx_port sa_socket +#endif /*! * @struct of_socket_address_t socket.h ObjFW/socket.h * * @brief A struct which represents a host / port pair for a socket. @@ -121,10 +147,11 @@ of_socket_address_family_t family; union { struct sockaddr sockaddr; struct sockaddr_in in; struct sockaddr_in6 in6; + struct sockaddr_ipx ipx; } sockaddr; socklen_t length; }; typedef struct of_socket_address_t of_socket_address_t; @@ -149,21 +176,29 @@ * @return The parsed IPv4 and port as an of_socket_address_t */ extern of_socket_address_t of_socket_address_parse_ipv4( OFString *IP, uint16_t port); -#ifdef OF_HAVE_IPV6 /*! * @brief Parses the specified IPv6 and port into an of_socket_address_t. * * @param IP The IPv6 to parse * @param port The port to use * @return The parsed IPv6 and port as an of_socket_address_t */ extern of_socket_address_t of_socket_address_parse_ipv6( OFString *IP, uint16_t port); -#endif + +/*! + * @brief Creates an IPX address for the specified network, node and port. + * + * @param network The IPX network + * @param node The node in the IPX network + * @param port The IPX port (sometimes called socket number) on the node + */ +extern of_socket_address_t of_socket_address_ipx(uint32_t network, + const unsigned char node[_Nonnull IPX_NODE_LEN], uint16_t port); /*! * @brief Compares two of_socket_address_t for equality. * * @param address1 The address to compare with the second address @@ -212,10 +247,22 @@ * @return The port of the address */ extern uint16_t of_socket_address_get_port( const of_socket_address_t *_Nonnull address); +/*! + * @brief Gets the IPX network, node and port from an IPX address. + * + * @param address The address on which to get the IPX network, node and port + * @param network The IPX network + * @param node A buffer to store the node + * @param port The IPX port (sometimes called socket number) on the node + */ +extern void of_socket_address_ipx_get( + const of_socket_address_t *_Nonnull address, uint32_t *_Nonnull network, + unsigned char node[_Nonnull IPX_NODE_LEN], uint16_t *_Nonnull port); + extern bool of_socket_init(void); #if defined(OF_HAVE_THREADS) && defined(OF_AMIGAOS) extern void of_socket_deinit(void); #endif extern int of_socket_errno(void); Index: src/socket.m ================================================================== --- src/socket.m +++ src/socket.m @@ -505,17 +505,42 @@ return of_socket_address_parse_ipv6(IP, port); } @catch (OFInvalidFormatException *e) { return of_socket_address_parse_ipv4(IP, port); } } + +of_socket_address_t +of_socket_address_ipx(uint32_t network, const unsigned char node[IPX_NODE_LEN], + uint16_t port) +{ + of_socket_address_t ret; + + memset(&ret, '\0', sizeof(ret)); + ret.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + ret.length = sizeof(ret.sockaddr.ipx); + +#ifdef AF_IPX + ret.sockaddr.ipx.sipx_family = AF_IPX; +#else + ret.sockaddr.ipx.sipx_family = AF_UNSPEC; +#endif + network = OF_BSWAP32_IF_LE(network); + memcpy(&ret.sockaddr.ipx.sipx_network, &network, + sizeof(ret.sockaddr.ipx.sipx_network)); + memcpy(ret.sockaddr.ipx.sipx_node, node, IPX_NODE_LEN); + ret.sockaddr.ipx.sipx_port = OF_BSWAP16_IF_LE(port); + + return ret; +} bool of_socket_address_equal(const of_socket_address_t *address1, const of_socket_address_t *address2) { const struct sockaddr_in *addrIn1, *addrIn2; const struct sockaddr_in6 *addrIn6_1, *addrIn6_2; + const struct sockaddr_ipx *addrIPX1, *addrIPX2; if (address1->family != address2->family) return false; switch (address1->family) { @@ -551,10 +576,28 @@ if (memcmp(addrIn6_1->sin6_addr.s6_addr, addrIn6_2->sin6_addr.s6_addr, sizeof(addrIn6_1->sin6_addr.s6_addr)) != 0) return false; + break; + case OF_SOCKET_ADDRESS_FAMILY_IPX: + if (address1->length < (socklen_t)sizeof(struct sockaddr_ipx) || + address2->length < (socklen_t)sizeof(struct sockaddr_ipx)) + @throw [OFInvalidArgumentException exception]; + + addrIPX1 = &address1->sockaddr.ipx; + addrIPX2 = &address2->sockaddr.ipx; + + if (addrIPX1->sipx_port != addrIPX2->sipx_port) + return false; + if (memcmp(&addrIPX1->sipx_network, &addrIPX2->sipx_network, + 4) != 0) + return false; + if (memcmp(addrIPX1->sipx_node, addrIPX2->sipx_node, + IPX_NODE_LEN) != 0) + return false; + break; default: @throw [OFInvalidArgumentException exception]; } @@ -597,10 +640,30 @@ for (size_t i = 0; i < sizeof(address->sockaddr.in6.sin6_addr.s6_addr); i++) OF_HASH_ADD(hash, address->sockaddr.in6.sin6_addr.s6_addr[i]); + break; + case OF_SOCKET_ADDRESS_FAMILY_IPX:; + unsigned char network[ + sizeof(address->sockaddr.ipx.sipx_network)]; + + if (address->length < (socklen_t)sizeof(struct sockaddr_ipx)) + @throw [OFInvalidArgumentException exception]; + + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_port >> 8); + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_port); + + memcpy(network, &address->sockaddr.ipx.sipx_network, + sizeof(network)); + + for (size_t i = 0; i < sizeof(network); i++) + OF_HASH_ADD(hash, network[i]); + + for (size_t i = 0; i < IPX_NODE_LEN; i++) + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_node[i]); + break; default: @throw [OFInvalidArgumentException exception]; } @@ -730,9 +793,24 @@ switch (address->family) { case OF_SOCKET_ADDRESS_FAMILY_IPV4: return OF_BSWAP16_IF_LE(address->sockaddr.in.sin_port); case OF_SOCKET_ADDRESS_FAMILY_IPV6: return OF_BSWAP16_IF_LE(address->sockaddr.in6.sin6_port); + case OF_SOCKET_ADDRESS_FAMILY_IPX: + return OF_BSWAP16_IF_LE(address->sockaddr.ipx.sipx_port); default: @throw [OFInvalidArgumentException exception]; } } + +void +of_socket_address_ipx_get(const of_socket_address_t *address, uint32_t *network, + unsigned char node[IPX_NODE_LEN], uint16_t *port) +{ + if (address->family != OF_SOCKET_ADDRESS_FAMILY_IPX) + @throw [OFInvalidArgumentException exception]; + + memcpy(network, &address->sockaddr.ipx.sipx_network, sizeof(*network)); + *network = OF_BSWAP32_IF_LE(*network); + memcpy(node, address->sockaddr.ipx.sipx_node, IPX_NODE_LEN); + *port = OF_BSWAP16_IF_LE(address->sockaddr.ipx.sipx_port); +} Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -16,10 +16,11 @@ ${OFBLOCKTESTS_M} \ OFCharacterSetTests.m \ OFDataTests.m \ OFDateTests.m \ OFDictionaryTests.m \ + ${OFIPXSOCKETTESTS_M} \ OFInvocationTests.m \ OFJSONTests.m \ OFListTests.m \ OFLocaleTests.m \ OFMethodSignatureTests.m \ ADDED tests/OFIPXSocketTests.m Index: tests/OFIPXSocketTests.m ================================================================== --- tests/OFIPXSocketTests.m +++ tests/OFIPXSocketTests.m @@ -0,0 +1,68 @@ +/* + * 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 = @"OFIPXSocket"; + +@implementation TestsAppDelegate (OFIPXSocketTests) +- (void)IPXSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFIPXSocket *sock; + of_socket_address_t address1, address2; + char buffer[5]; + + TEST(@"+[socket]", (sock = [OFIPXSocket socket])) + + @try { + TEST(@"-[bindToPort:packetType:]", + R(address1 = [sock bindToPort: 0 + packetType: 0])) + } @catch (OFBindFailedException *e) { + if (e.errNo != EAFNOSUPPORT) + @throw e; + + [self outputString: @"[OFIPXSocket] -[bindToPort:packetType:]: " + @"IPX unsupported, skipping tests\n" + inColor: GREEN]; + + objc_autoreleasePoolPop(pool); + return; + } + + 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 && + of_socket_address_equal(&address1, &address2) && + of_socket_address_hash(&address1) == + of_socket_address_hash(&address2)) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -139,10 +139,14 @@ @end @interface TestsAppDelegate (OFINIFileTests) - (void)INIFileTests; @end + +@interface TestsAppDelegate (OFIPXSocketTests) +- (void)IPXSocketTests; +@end @interface TestsAppDelegate (OFInvocationTests) - (void)invocationTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -413,10 +413,13 @@ #endif #ifdef OF_HAVE_SOCKETS [self socketTests]; [self TCPSocketTests]; [self UDPSocketTests]; +# ifdef OF_HAVE_IPX + [self IPXSocketTests]; +# endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests]; #endif