Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -77,13 +77,14 @@ TLS = @TLS@ TLS_CPPFLAGS = @TLS_CPPFLAGS@ TLS_LIBS = @TLS_LIBS@ UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ +USE_SRCS_APPLETALK = @USE_SRCS_APPLETALK@ 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 @@ -134,12 +134,14 @@ OFSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFTLSStream.m \ OFUDPSocket.m \ + ${USE_SRCS_APPLETALK} \ ${USE_SRCS_IPX} \ ${USE_SRCS_UNIX_SOCKETS} +SRCS_APPLETALK = OFDDPSocket.m SRCS_IPX = OFIPXSocket.m \ OFSPXSocket.m \ OFSPXStreamSocket.m SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m \ OFUNIXStreamSocket.m ADDED src/OFDDPSocket.h Index: src/OFDDPSocket.h ================================================================== --- src/OFDDPSocket.h +++ src/OFDDPSocket.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008-2022 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 OFDDPSocketDelegate OFDDPSocket.h ObjFW/OFDDPSocket.h + * + * @brief A delegate for OFDDPSocket. + */ +@protocol OFDDPSocketDelegate +@end + +/** + * @class OFDDPSocket OFDDPSocket.h ObjFW/OFDDPSocket.h + * + * @brief A class which provides methods to create and use AppleTalk DDP + * sockets. + * + * Addresses are of type @ref OFSocketAddress. You can use + * @ref OFSocketAddressMakeAppleTalk to create an address or + * @ref OFSocketAddressAppleTalkNetwork to get the AppleTalk network, + * @ref OFSocketAddressAppleTalkNode to get the AppleTalk node and + * @ref OFSocketAddressPort to get the port (sometimes 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 OFDDPSocket: OFDatagramSocket +{ + OF_RESERVE_IVARS(OFDDPSocket, 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 network, node and port with the + * specified packet type. + * + * @param port The port to bind to. 0 means to pick one and return it via the + * returned socket address. + * @return The address on which this socket can be reached + * @throw OFBindFailedException Binding failed + * @throw OFAlreadyConnectedException The socket is already bound + */ +- (OFSocketAddress)bindToPort: (uint8_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFDDPSocket.m Index: src/OFDDPSocket.m ================================================================== --- src/OFDDPSocket.m +++ src/OFDDPSocket.m @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2008-2022 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 "OFDDPSocket.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" + +@implementation OFDDPSocket +@dynamic delegate; + +- (OFSocketAddress)bindToPort: (uint8_t)port +{ + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = OFSocketAddressMakeAppleTalk(0, 0, port); + + if ((_socket = socket(address.sockaddr.at.sat_family, + SOCK_DGRAM | SOCK_CLOEXEC, 0)) == OFInvalidSocketHandle) + @throw [OFBindFailedException + exceptionWithPort: port + 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, (struct sockaddr *)&address.sockaddr, + address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPort: port + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + address.family = OFSocketAddressFamilyAppleTalk; + address.length = (socklen_t)sizeof(address.sockaddr); + + if (OFGetSockName(_socket, (struct sockaddr *)&address.sockaddr, + &address.length) != 0) { + int errNo = OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPort: port + socket: self + errNo: errNo]; + } + + if (address.sockaddr.at.sat_family != AF_APPLETALK) { + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindFailedException exceptionWithPort: port + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -82,24 +82,23 @@ # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" +# ifdef OF_HAVE_UNIX_SOCKETS +# import "OFUNIXDatagramSocket.h" +# import "OFUNIXStreamSocket.h" +# endif # ifdef OF_HAVE_IPX # import "OFIPXSocket.h" # 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" -# endif +# ifdef OF_HAVE_APPLETALK +# import "OFDDPSocket.h" +# endif +# import "OFHTTPClient.h" # import "OFHTTPCookie.h" # import "OFHTTPCookieManager.h" # import "OFHTTPRequest.h" # import "OFHTTPResponse.h" # import "OFHTTPServer.h" Index: src/exceptions/OFBindFailedException.h ================================================================== --- src/exceptions/OFBindFailedException.h +++ src/exceptions/OFBindFailedException.h @@ -85,26 +85,10 @@ + (instancetype)exceptionWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo; -+ (instancetype)exception OF_UNAVAILABLE; - -/** - * @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; - /** * @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 @@ -113,10 +97,38 @@ */ + (instancetype)exceptionWithPath: (OFString *)path socket: (id)socket errNo: (int)errNo; +/** + * @brief Creates a new, autoreleased bind failed exception. + * + * @param port The port to 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 + socket: (id)socket + errNo: (int)errNo; + +/** + * @brief Creates a new, autoreleased bind failed exception. + * + * @param port The 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)exception OF_UNAVAILABLE; + /** * @brief Initializes an already allocated bind failed exception. * * @param host The host on which binding failed * @param port The port on which binding failed @@ -126,24 +138,10 @@ */ - (instancetype)initWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket 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; /** * @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 @@ -152,9 +150,35 @@ */ - (instancetype)initWithPath: (OFString *)path socket: (id)socket errNo: (int)errNo; +/** + * @brief Initializes an already allocated bind failed exception. + * + * @param port The port to 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 + socket: (id)socket + errNo: (int)errNo; + +/** + * @brief Initializes an already allocated bind failed exception. + * + * @param port The 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; + - (instancetype)init OF_UNAVAILABLE; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindFailedException.m ================================================================== --- src/exceptions/OFBindFailedException.m +++ src/exceptions/OFBindFailedException.m @@ -35,28 +35,37 @@ return [[[self alloc] initWithHost: host 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)exceptionWithPort: (uint16_t)port + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithPort: port + socket: sock + errNo: errNo] autorelease]; +} + (instancetype)exceptionWithPort: (uint16_t)port packetType: (uint8_t)packetType socket: (id)sock errNo: (int)errNo { 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 { @@ -81,20 +90,18 @@ } return self; } -- (instancetype)initWithPort: (uint16_t)port - packetType: (uint8_t)packetType +- (instancetype)initWithPath: (OFString *)path socket: (id)sock errNo: (int)errNo { self = [super init]; @try { - _port = port; - _packetType = packetType; + _path = [path copy]; _socket = [sock retain]; _errNo = errNo; } @catch (id e) { [self release]; @throw e; @@ -101,18 +108,30 @@ } return self; } -- (instancetype)initWithPath: (OFString *)path +- (instancetype)initWithPort: (uint16_t)port + socket: (id)sock + errNo: (int)errNo +{ + return [self initWithPort: port + packetType: 0 + socket: sock + errNo: errNo]; +} + +- (instancetype)initWithPort: (uint16_t)port + packetType: (uint8_t)packetType socket: (id)sock errNo: (int)errNo { self = [super init]; @try { - _path = [path copy]; + _port = port; + _packetType = packetType; _socket = [sock retain]; _errNo = errNo; } @catch (id e) { [self release]; @throw e; @@ -139,12 +158,17 @@ 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 + else if (_port != 0) return [OFString stringWithFormat: @"Binding to port %" @PRIx16 @" for packet type %" @PRIx8 @" failed in socket of type %@: %@", _port, _packetType, [_socket class], OFStrError(_errNo)]; + else + return [OFString stringWithFormat: + @"Binding to port %" @PRIx16 @" failed in socket of type " + @"%@: %@", + _port, [_socket class], OFStrError(_errNo)]; } @end Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -73,12 +73,14 @@ OFHTTPCookieManagerTests.m \ OFKernelEventObserverTests.m \ OFSocketTests.m \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ + ${USE_SRCS_APPLETALK} \ ${USE_SRCS_IPX} \ ${USE_SRCS_UNIX_SOCKETS} +SRCS_APPLETALK = OFDDPSocketTests.m SRCS_IPX = OFIPXSocketTests.m \ OFSPXSocketTests.m \ OFSPXStreamSocketTests.m SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m \ OFUNIXStreamSocketTests.m ADDED tests/OFDDPSocketTests.m Index: tests/OFDDPSocketTests.m ================================================================== --- tests/OFDDPSocketTests.m +++ tests/OFDDPSocketTests.m @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008-2022 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 = @"OFDDPSocket"; + +@implementation TestsAppDelegate (OFDDPSocketTests) +- (void)DDPSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFDDPSocket *sock; + OFSocketAddress address1, address2; + char buffer[5]; + + TEST(@"+[socket]", (sock = [OFDDPSocket socket])) + + @try { + TEST(@"-[bindToPort:]", R(address1 = [sock bindToPort: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EAFNOSUPPORT: + [OFStdOut setForegroundColor: [OFColor lime]]; + [OFStdOut writeLine: + @"\r[OFDDPSocket] -[bindToPort:] " + @"AppleTalk unsupported, skipping tests"]; + break; + case EADDRNOTAVAIL: + [OFStdOut setForegroundColor: [OFColor lime]]; + [OFStdOut writeLine: + @"\r[OFDDPSocket] -[bindToPort:]: " + @"AppleTalk not configured, skipping tests"]; + break; + default: + @throw e; + } + + 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 && + OFSocketAddressEqual(&address1, &address2) && + OFSocketAddressHash(&address1) == OFSocketAddressHash(&address2)) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -68,10 +68,14 @@ @end @interface TestsAppDelegate (OFCharacterSetTests) - (void)characterSetTests; @end + +@interface TestsAppDelegate (OFDDPSocketTests) +- (void)DDPSocketTests; +@end @interface TestsAppDelegate (OFDNSResolverTests) - (void)DNSResolverTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -409,18 +409,21 @@ #endif #ifdef OF_HAVE_SOCKETS [self socketTests]; [self TCPSocketTests]; [self UDPSocketTests]; +# ifdef OF_HAVE_UNIX_SOCKETS + [self UNIXDatagramSocketTests]; + [self UNIXStreamSocketTests]; +# endif # ifdef OF_HAVE_IPX [self IPXSocketTests]; [self SPXSocketTests]; [self SPXStreamSocketTests]; # endif -# ifdef OF_HAVE_UNIX_SOCKETS - [self UNIXDatagramSocketTests]; - [self UNIXStreamSocketTests]; +# ifdef OF_HAVE_APPLETALK + [self DDPSocketTests]; # endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests];