Index: .github/workflows/ubuntu-20.04-gcc.yml ================================================================== --- .github/workflows/ubuntu-20.04-gcc.yml +++ .github/workflows/ubuntu-20.04-gcc.yml @@ -23,11 +23,11 @@ - --with-tls=gnutls --disable-shared steps: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install gobjc libssl-dev gnutls-dev + sudo apt-get install gobjc libsctp-dev libssl-dev gnutls-dev - uses: actions/checkout@v4 - name: autogen.sh run: ./autogen.sh - name: configure run: ./configure OBJC="gcc" ${{ matrix.configure_flags }} Index: .github/workflows/ubuntu-20.04.yml ================================================================== --- .github/workflows/ubuntu-20.04.yml +++ .github/workflows/ubuntu-20.04.yml @@ -23,11 +23,11 @@ - --with-tls=gnutls --disable-shared steps: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install libssl-dev gnutls-dev + sudo apt-get install libsctp-dev libssl-dev gnutls-dev - uses: actions/checkout@v4 - name: autogen.sh run: ./autogen.sh - name: configure run: ./configure ${{ matrix.configure_flags }} Index: .github/workflows/ubuntu-latest-gcc.yml ================================================================== --- .github/workflows/ubuntu-latest-gcc.yml +++ .github/workflows/ubuntu-latest-gcc.yml @@ -25,11 +25,11 @@ - --with-tls=mbedtls --disable-shared steps: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install gobjc libssl-dev gnutls-dev libmbedtls-dev + sudo apt-get install gobjc libsctp-dev libssl-dev gnutls-dev libmbedtls-dev - uses: actions/checkout@v4 - name: autogen.sh run: ./autogen.sh - name: configure run: ./configure OBJC="gcc" ${{ matrix.configure_flags }} Index: .github/workflows/ubuntu-latest.yml ================================================================== --- .github/workflows/ubuntu-latest.yml +++ .github/workflows/ubuntu-latest.yml @@ -25,11 +25,11 @@ - --with-tls=mbedtls --disable-shared steps: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install libssl-dev gnutls-dev libmbedtls-dev + sudo apt-get install libsctp-dev libssl-dev gnutls-dev libmbedtls-dev - uses: actions/checkout@v4 - name: autogen.sh run: ./autogen.sh - name: configure run: ./configure ${{ matrix.configure_flags }} Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1392,10 +1392,18 @@ [Whether we have netinet/in.h]) ]) AC_CHECK_HEADER(netinet/tcp.h, [ AC_DEFINE(OF_HAVE_NETINET_TCP_H, 1, [Whether we have netinet/tcp.h]) + ]) + AC_CHECK_HEADER(netinet/sctp.h, [ + AC_SEARCH_LIBS(sctp_recvv, sctp, [ + AC_DEFINE(OF_HAVE_SCTP, 1, [Whether we have SCTP]) + AC_DEFINE(OF_HAVE_NETINET_SCTP_H, 1, + [Whether we have netinet/sctp.h]) + AC_SUBST(USE_SRCS_SCTP, '${SRCS_SCTP}') + ]) ]) AC_CHECK_HEADERS([arpa/inet.h netdb.h sys/sockio.h]) AC_CHECK_HEADERS([net/if.h], [], [], [ #ifdef OF_HAVE_SYS_SOCKET_H # include Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -61,10 +61,11 @@ OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ OF_MBEDTLS_TLS_STREAM_M = @OF_MBEDTLS_TLS_STREAM_M@ OF_OPENSSL_TLS_STREAM_M = @OF_OPENSSL_TLS_STREAM_M@ OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ +OF_SCTP_SOCKET_M = @OF_SCTP_SOCKET_M@ OF_SECURE_TRANSPORT_TLS_STREAM_M = @OF_SECURE_TRANSPORT_TLS_STREAM_M@ OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ @@ -86,13 +87,14 @@ 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_SCTP = @USE_SRCS_SCTP@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_SUBPROCESSES = @USE_SRCS_SUBPROCESSES@ USE_SRCS_TAGGED_POINTERS = @USE_SRCS_TAGGED_POINTERS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ USE_SRCS_UNIX_SOCKETS = @USE_SRCS_UNIX_SOCKETS@ USE_SRCS_WINDOWS = @USE_SRCS_WINDOWS@ WII_U_TESTS_LIBS = @WII_U_TESTS_LIBS@ WRAPPER = @WRAPPER@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -151,15 +151,17 @@ OFTXTDNSResourceRecord.m \ OFUDPSocket.m \ OFURIDNSResourceRecord.m \ ${USE_SRCS_APPLETALK} \ ${USE_SRCS_IPX} \ + ${USE_SRCS_SCTP} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_APPLETALK = OFDDPSocket.m SRCS_IPX = OFIPXSocket.m \ OFSPXSocket.m \ OFSPXStreamSocket.m +SRCS_SCTP = OFSCTPSocket.m SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m \ OFUNIXStreamSocket.m SRCS_SUBPROCESSES = OFSubprocess.m SRCS_THREADS = OFCondition.m \ OFMutex.m \ Index: src/OFAsyncIPSocketConnector.m ================================================================== --- src/OFAsyncIPSocketConnector.m +++ src/OFAsyncIPSocketConnector.m @@ -21,10 +21,13 @@ #include #import "OFAsyncIPSocketConnector.h" #import "OFData.h" +#ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +#endif #import "OFTCPSocket.h" #import "OFThread.h" #import "OFTimer.h" #import "OFConnectIPSocketFailedException.h" @@ -72,10 +75,14 @@ #ifdef OF_HAVE_BLOCKS if (_block != NULL) { if ([_socket isKindOfClass: [OFTCPSocket class]]) ((OFTCPSocketAsyncConnectBlock)_block)(_exception); +# ifdef OF_HAVE_SCTP + else if ([_socket isKindOfClass: [OFSCTPSocket class]]) + ((OFSCTPSocketAsyncConnectBlock)_block)(_exception); +# endif else OFEnsure(0); } else { #endif if ([_delegate respondsToSelector: Index: src/OFRunLoop+Private.h ================================================================== --- src/OFRunLoop+Private.h +++ src/OFRunLoop+Private.h @@ -21,10 +21,13 @@ #import "OFStream.h" #ifdef OF_HAVE_SOCKETS # import "OFDatagramSocket.h" # import "OFSequencedPacketSocket.h" # import "OFStreamSocket.h" +# ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +# endif #endif OF_ASSUME_NONNULL_BEGIN #ifdef OF_HAVE_SOCKETS @@ -96,11 +99,11 @@ length: (size_t)length mode: (OFRunLoopMode)mode # ifdef OF_HAVE_BLOCKS block: (nullable OFDatagramSocketAsyncReceiveBlock)block # endif - delegate: (nullable id ) delegate; + delegate: (nullable id )delegate; + (void)of_addAsyncSendForDatagramSocket: (OFDatagramSocket *)socket data: (OFData *)data receiver: (const OFSocketAddress *)receiver mode: (OFRunLoopMode)mode # ifdef OF_HAVE_BLOCKS @@ -113,20 +116,40 @@ length: (size_t)length mode: (OFRunLoopMode)mode # ifdef OF_HAVE_BLOCKS block: (nullable OFSequencedPacketSocketAsyncReceiveBlock)block # endif - delegate: (nullable id ) delegate; + delegate: (nullable id )delegate; + (void)of_addAsyncSendForSequencedPacketSocket: (OFSequencedPacketSocket *)socket data: (OFData *)data mode: (OFRunLoopMode)mode # ifdef OF_HAVE_BLOCKS block: (nullable OFSequencedPacketSocketAsyncSendDataBlock)block # endif delegate: (nullable id )delegate; +# ifdef OF_HAVE_SCTP ++ (void)of_addAsyncReceiveForSCTPSocket: (OFSCTPSocket *)socket + buffer: (void *)buffer + length: (size_t)length + mode: (OFRunLoopMode)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable OFSCTPSocketAsyncReceiveBlock)block +# endif + delegate: (nullable id )delegate; ++ (void)of_addAsyncSendForSCTPSocket: (OFSCTPSocket *)socket + data: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + mode: (OFRunLoopMode)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable OFSCTPSocketAsyncSendDataBlock)block +# endif + delegate: (nullable id )delegate; +# endif + (void)of_cancelAsyncRequestsForObject: (id)object mode: (OFRunLoopMode)mode; #endif - (void)of_removeTimer: (OFTimer *)timer forMode: (OFRunLoopMode)mode; @end OF_ASSUME_NONNULL_END Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -200,10 +200,36 @@ OFSequencedPacketSocketAsyncSendDataBlock _block; # endif OFData *_data; } @end + +# ifdef OF_HAVE_SCTP +@interface OFRunLoopSCTPReceiveQueueItem: OFRunLoopQueueItem +{ +@public +# ifdef OF_HAVE_BLOCKS + OFSCTPSocketAsyncReceiveBlock _block; +# endif + void *_buffer; + size_t _length; +} +@end + +@interface OFRunLoopSCTPSendQueueItem: OFRunLoopQueueItem +{ +@public +# ifdef OF_HAVE_BLOCKS + OFSCTPSocketAsyncSendDataBlock _block; +# endif + OFData *_data; + uint16_t _streamID; + uint32_t _PPID; + OFSCTPMessageFlags _flags; +} +@end +# endif #endif @implementation OFRunLoopState - (instancetype)init { @@ -1006,10 +1032,129 @@ # endif [super dealloc]; } @end + +# ifdef OF_HAVE_SCTP +@implementation OFRunLoopSCTPReceiveQueueItem +- (bool)handleObject: (id)object +{ + size_t length; + uint16_t streamID; + uint32_t PPID; + OFSCTPMessageFlags flags; + id exception = nil; + + @try { + length = [object receiveIntoBuffer: _buffer + length: _length + streamID: &streamID + PPID: &PPID + flags: &flags]; + } @catch (id e) { + length = 0; + exception = e; + } + +# ifdef OF_HAVE_BLOCKS + if (_block != NULL) + return _block(length, streamID, PPID, flags, exception); + else { +# endif + if (![_delegate respondsToSelector: @selector(socket: + didReceiveIntoBuffer:length:streamID:PPID:flags: + exception:)]) + return false; + + return [_delegate socket: object + didReceiveIntoBuffer: _buffer + length: length + streamID: streamID + PPID: PPID + flags: flags + exception: exception]; +# ifdef OF_HAVE_BLOCKS + } +# endif +} + +# ifdef OF_HAVE_BLOCKS +- (void)dealloc +{ + [_block release]; + + [super dealloc]; +} +# endif +@end + +@implementation OFRunLoopSCTPSendQueueItem +- (bool)handleObject: (id)object +{ + id exception = nil; + OFData *newData, *oldData; + + @try { + [object sendBuffer: _data.items + length: _data.count * _data.itemSize + streamID: _streamID + PPID: _PPID + flags: _flags]; + } @catch (id e) { + exception = e; + } + +# ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + newData = _block(exception); + + if (newData == nil) + return false; + + oldData = _data; + _data = [newData copy]; + [oldData release]; + + return true; + } else { +# endif + if (![_delegate respondsToSelector: @selector(socket: + didSendData:streamID:PPID:flags:exception:)]) + return false; + + newData = [_delegate socket: object + didSendData: _data + streamID: _streamID + PPID: _PPID + flags: _flags + exception: exception]; + + if (newData == nil) + return false; + + oldData = _data; + _data = [newData copy]; + [oldData release]; + + return true; +# ifdef OF_HAVE_BLOCKS + } +# endif +} + +- (void)dealloc +{ + [_data release]; +# ifdef OF_HAVE_BLOCKS + [_block release]; +# endif + + [super dealloc]; +} +@end +# endif #endif @implementation OFRunLoop @synthesize currentMode = _currentMode; @@ -1314,10 +1459,58 @@ # endif queueItem->_data = [data copy]; QUEUE_ITEM } + +# ifdef OF_HAVE_SCTP ++ (void)of_addAsyncReceiveForSCTPSocket: (OFSCTPSocket *)sock + buffer: (void *)buffer + length: (size_t)length + mode: (OFRunLoopMode)mode +# ifdef OF_HAVE_BLOCKS + block: (OFSCTPSocketAsyncReceiveBlock)block +# endif + delegate: (id )delegate +{ + NEW_READ(OFRunLoopSCTPReceiveQueueItem, sock, mode) + + queueItem->_delegate = [delegate retain]; +# ifdef OF_HAVE_BLOCKS + queueItem->_block = [block copy]; +# endif + queueItem->_buffer = buffer; + queueItem->_length = length; + + QUEUE_ITEM +} + ++ (void)of_addAsyncSendForSCTPSocket: (OFSCTPSocket *)sock + data: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + mode: (OFRunLoopMode)mode +# ifdef OF_HAVE_BLOCKS + block: (OFSCTPSocketAsyncSendDataBlock)block +# endif + delegate: (id )delegate +{ + NEW_WRITE(OFRunLoopSCTPSendQueueItem, sock, mode) + + queueItem->_delegate = [delegate retain]; +# ifdef OF_HAVE_BLOCKS + queueItem->_block = [block copy]; +# endif + queueItem->_data = [data copy]; + queueItem->_streamID = streamID; + queueItem->_PPID = PPID; + queueItem->_flags = flags; + + QUEUE_ITEM +} +# endif # undef NEW_READ # undef NEW_WRITE # undef QUEUE_ITEM + (void)of_cancelAsyncRequestsForObject: (id)object mode: (OFRunLoopMode)mode ADDED src/OFSCTPSocket.h Index: src/OFSCTPSocket.h ================================================================== --- src/OFSCTPSocket.h +++ src/OFSCTPSocket.h @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFSequencedPacketSocket.h" +#import "OFRunLoop.h" + +OF_ASSUME_NONNULL_BEGIN + +/** @file */ + +@class OFSCTPSocket; +@class OFString; + +/** + * @brief Flags for an SCTP message. + */ +typedef enum { + /** The message is sent / received out of order. */ + OFSCTPMessageUnordered = 1 +} OFSCTPMessageFlags; + +#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 (^OFSCTPSocketAsyncConnectBlock)(id _Nullable exception); + +/** + * @brief A block which is called when a message has been received. + * + * @param length The length of the message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param exception An exception which occurred while receiving or `nil` on + * success + * @return A bool whether the same block should be used for the next receive + */ +typedef bool (^OFSCTPSocketAsyncReceiveBlock)(size_t length, uint16_t streamID, + uint32_t PPID, OFSCTPMessageFlags flags, id _Nullable exception); + +/** + * @brief A block which is called when a message has been sent. + * + * @param exception An exception which occurred while reading or `nil` on + * success + * @return The data to repeat the send with or nil if it should not repeat + */ +typedef OFData *_Nullable (^OFSCTPSocketAsyncSendDataBlock)( + id _Nullable exception); +#endif + +/** + * @protocol OFSCTPSocketDelegate OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * A delegate for OFSCTPSocket. + */ +@protocol OFSCTPSocketDelegate +@optional +/** + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param host The host connected to + * @param port The port on the host connected to + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSCTPSocket *)socket + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (nullable id)exception; + +/** + * @brief This method is called when a message has been received. + * + * @param socket The SCTP socket which received a message + * @param buffer The buffer the message has been written to + * @param length The length of the message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param exception An exception that occurred while receiving, or nil on + * success + * @return A bool whether the same block should be used for the next receive + */ +- (bool)socket: (OFSCTPSocket *)socket + didReceiveIntoBuffer: (void *)buffer + length: (size_t)length + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + exception: (nullable id)exception; + +/** + * @brief This method is called when a message has been sent. + * + * @param socket The SCTP socket which sent a message + * @param data The data which was sent + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param exception An exception that occurred while sending, or nil on success + * @return The data to repeat the send with or nil if it should not repeat + */ +- (nullable OFData *)socket: (OFSCTPSocket *)socket + didSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + exception: (nullable id)exception; +@end + +/** + * @class OFSCTPSocket OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * @brief A class which provides methods to create and use SCTP sockets in + * one-to-one mode. + * + * 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 OFSCTPSocket: OFSequencedPacketSocket +{ + OF_RESERVE_IVARS(OFSCTPSocket, 4) +} + +/** + * @brief Whether sending messages can be delayed. Setting this to NO sets + * SCTP_NODELAY on the socket. + * + * @throw OFGetOptionFailedException The option could not be retrieved + * @throw OFSetOptionFailedException The option could not be set + */ +@property (nonatomic) bool canDelaySendingMessages; + +/** + * @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 OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @throw OFConnectIPSocketFailedException Connecting failed + * @throw OFAlreadyOpenException The socket is already connected or bound + */ +- (void)connectToHost: (OFString *)host port: (uint16_t)port; + +/** + * @brief Asynchronously connect the OFSCTPSocket 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 OFSCTPSocket 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 + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (OFRunLoopMode)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/** + * @brief Asynchronously connect the OFSCTPSocket 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 + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (OFSCTPSocketAsyncConnectBlock)block; + +/** + * @brief Asynchronously connect the OFSCTPSocket 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 + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncConnectBlock)block; +#endif + +/** + * @brief Bind 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. + * @return The address the socket was bound to + * @throw OFBindIPSocketFailedException Binding failed + * @throw OFAlreadyOpenException The socket is already connected or bound + */ +- (OFSocketAddress)bindToHost: (OFString *)host port: (uint16_t)port; + +/** + * @brief Receives a message for the specified stream ID and stores it into the + * specified buffer. + * + * If the buffer is too small, the message is truncated. + * + * @param buffer The buffer to write the message to + * @param length The length of the buffer + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @return The length of the received message + * @throw OFReadFailedException Receiving failed + * @throw OFNotOpenException The socket is not open + */ +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length + streamID: (nullable uint16_t *)streamID + PPID: (nullable uint32_t *)PPID + flags: (nullable OFSCTPMessageFlags *)flags; + +/** + * @brief Asynchronously receives a message with stream ID and PPID and stores + * it into the specified buffer. + * + * If the buffer is too small, the message is truncated. + * + * @param buffer The buffer to write the message to + * @param length The length of the buffer + */ +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length; + +/** + * @brief Asynchronously receives a message with stream ID and PPID and stores + * it into the specified buffer. + * + * If the buffer is too small, the message is truncated. + * + * @param buffer The buffer to write the message to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the asynchronous + * receive + */ +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (OFRunLoopMode)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/** + * @brief Asynchronously receives a message with stream ID and PPID and stores + * it into the specified buffer. + * + * If the buffer is too small, the message is truncated. + * + * @param buffer The buffer to write the message to + * @param length The length of the buffer + * @param block The block to call when the message has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more messages have been received. + * If you want the next method in the queue to handle the message + * received next, you need to return false from the method. + */ +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + block: (OFSCTPSocketAsyncReceiveBlock)block; + +/** + * @brief Asynchronously receives a message with stream ID and PPID and stores + * it into the specified buffer. + * + * If the buffer is too small, the message is truncated. + * + * @param buffer The buffer to write the message to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the asynchronous + * receive + * @param block The block to call when the message has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more messages have been received. + * If you want the next method in the queue to handle the message + * received next, you need to return false from the method. + */ +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncReceiveBlock)block; +#endif + +/** + * @brief Sends the specified message on the specified stream. + * + * @param buffer The buffer to send as a message + * @param length The length of the buffer + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @throw OFWriteFailedException Sending failed + * @throw OFNotOpenException The socket is not open + */ +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags; + +/** + * @brief Asynchronously sends the specified message on the specified stream. + * + * @param data The data to send as a message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + */ +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags; + +/** + * @brief Asynchronously sends the specified message on the specified stream. + * + * @param data The data to send as a message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param runLoopMode The run loop mode in which to perform the asynchronous + * send + */ +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + runLoopMode: (OFRunLoopMode)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/** + * @brief Asynchronously sends the specified message on the specified stream. + * + * @param data The data to send as a message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param block The block to call when the message has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + block: (OFSCTPSocketAsyncSendDataBlock)block; + +/** + * @brief Asynchronously sends the specified message on the specified stream. + * + * @param data The data to send as a message + * @param streamID The stream ID for the message + * @param PPID The Payload Protocol Identifier for the message + * @param flags Flags for the message + * @param runLoopMode The run loop mode in which to perform the asynchronous + * send + * @param block The block to call when the message has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncSendDataBlock)block; +#endif +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSCTPSocket.m Index: src/OFSCTPSocket.m ================================================================== --- src/OFSCTPSocket.m +++ src/OFSCTPSocket.m @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFSCTPSocket.h" +#import "OFAsyncIPSocketConnector.h" +#import "OFDNSResolver.h" +#import "OFData.h" +#import "OFDate.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" +#import "OFSocket.h" +#import "OFSocket+Private.h" +#import "OFString.h" +#import "OFThread.h" + +#import "OFAcceptSocketFailedException.h" +#import "OFAlreadyOpenException.h" +#import "OFBindIPSocketFailedException.h" +#import "OFGetOptionFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFSetOptionFailedException.h" +#import "OFWriteFailedException.h" + +static const OFRunLoopMode connectRunLoopMode = + @"OFSCTPSocketConnectRunLoopMode"; + +@interface OFSCTPSocket () +@end + +@interface OFSCTPSocketConnectDelegate: OFObject +{ +@public + bool _done; + id _exception; +} +@end + +@implementation OFSCTPSocketConnectDelegate +- (void)dealloc +{ + [_exception release]; + + [super dealloc]; +} + +- (void)socket: (OFSCTPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + _done = true; + _exception = [exception retain]; +} +@end + +@implementation OFSCTPSocket +@dynamic delegate; + +- (bool)of_createSocketForAddress: (const OFSocketAddress *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif +#ifdef SCTP_RECVRCVINFO + int one = 1; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyOpenException exceptionWithObject: self]; + + if ((_socket = socket( + ((struct sockaddr *)&address->sockaddr)->sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == + OFInvalidSocketHandle) { + *errNo = _OFSocketErrNo(); + 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 + +#ifdef SCTP_RECVRCVINFO + if (setsockopt(_socket, IPPROTO_SCTP, SCTP_RECVRCVINFO, &one, + sizeof(one)) != 0) { + *errNo = _OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + return false; + } +#endif + + return true; +} + +- (bool)of_connectSocketToAddress: (const OFSocketAddress *)address + errNo: (int *)errNo +{ + if (_socket == OFInvalidSocketHandle) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, (struct sockaddr *)&address->sockaddr, + address->length) != 0) { + *errNo = _OFSocketErrNo(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = OFInvalidSocketHandle; +} + +- (void)connectToHost: (OFString *)host port: (uint16_t)port +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = _delegate; + OFSCTPSocketConnectDelegate *connectDelegate = + [[[OFSCTPSocketConnectDelegate alloc] init] autorelease]; + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + + _delegate = connectDelegate; + [self asyncConnectToHost: host + port: port + runLoopMode: connectRunLoopMode]; + + while (!connectDelegate->_done) + [runLoop runMode: connectRunLoopMode beforeDate: nil]; + + /* Cleanup */ + [runLoop runMode: connectRunLoopMode beforeDate: [OFDate date]]; + + _delegate = delegate; + + if (connectDelegate->_exception != nil) + @throw connectDelegate->_exception; + + objc_autoreleasePoolPop(pool); +} + +- (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port +{ + [self asyncConnectToHost: host + port: port + runLoopMode: OFDefaultRunLoopMode]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (OFRunLoopMode)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyOpenException exceptionWithObject: self]; + + [[[[OFAsyncIPSocketConnector alloc] + initWithSocket: self + host: host + port: port + delegate: _delegate + block: NULL + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (OFSCTPSocketAsyncConnectBlock)block +{ + [self asyncConnectToHost: host + port: port + runLoopMode: OFDefaultRunLoopMode + block: block]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncConnectBlock)block +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyOpenException exceptionWithObject: self]; + + [[[[OFAsyncIPSocketConnector alloc] + initWithSocket: self + host: host + port: port + delegate: nil + block: block] autorelease] + startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (OFSocketAddress)bindToHost: (OFString *)host port: (uint16_t)port +{ + const int one = 1; + void *pool = objc_autoreleasePoolPush(); + OFData *socketAddresses; + OFSocketAddress address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != OFInvalidSocketHandle) + @throw [OFAlreadyOpenException exceptionWithObject: self]; + + socketAddresses = [[OFThread DNSResolver] + resolveAddressesForHost: host + addressFamily: OFSocketAddressFamilyAny]; + + address = *(OFSocketAddress *)[socketAddresses itemAtIndex: 0]; + OFSocketAddressSetIPPort(&address, port); + + if ((_socket = socket( + ((struct sockaddr *)&address.sockaddr)->sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == OFInvalidSocketHandle) + @throw [OFBindIPSocketFailedException + exceptionWithHost: host + port: port + 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 + + setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, (socklen_t)sizeof(one)); + + if (bind(_socket, (struct sockaddr *)&address.sockaddr, + address.length) != 0) { + int errNo = _OFSocketErrNo(); + + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindIPSocketFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + + 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 [OFBindIPSocketFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + switch (((struct sockaddr *)&address.sockaddr)->sa_family) { + case AF_INET: + address.family = OFSocketAddressFamilyIPv4; + break; +# ifdef OF_HAVE_IPV6 + case AF_INET6: + address.family = OFSocketAddressFamilyIPv6; + break; +# endif + default: + closesocket(_socket); + _socket = OFInvalidSocketHandle; + + @throw [OFBindIPSocketFailedException + exceptionWithHost: host + port: port + socket: self + errNo: EAFNOSUPPORT]; + } + + objc_autoreleasePoolPop(pool); + + return address; +} + +#ifdef SCTP_RECVRCVINFO +- (instancetype)accept +{ + OFSCTPSocket *accepted = [super accept]; + int one = 1; + + if (setsockopt(accepted->_socket, IPPROTO_SCTP, SCTP_RECVRCVINFO, &one, + sizeof(one)) != 0) + @throw [OFAcceptSocketFailedException + exceptionWithSocket: self + errNo: _OFSocketErrNo()]; + + return accepted; +} +#endif + +- (size_t)receiveIntoBuffer: (void *)buffer length: (size_t)length +{ + return [self receiveIntoBuffer: buffer + length: length + streamID: NULL + PPID: NULL + flags: NULL]; +} + +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length + streamID: (uint16_t *)streamID + PPID: (uint32_t *)PPID + flags: (OFSCTPMessageFlags *)flags +{ + ssize_t ret; + struct iovec iov = { + .iov_base = buffer, + .iov_len = length + }; + struct sctp_rcvinfo rcvinfo; + socklen_t rcvinfoSize = (socklen_t)sizeof(rcvinfo); + unsigned int infotype = SCTP_RECVV_RCVINFO; + + if (_socket == OFInvalidSocketHandle) + @throw [OFNotOpenException exceptionWithObject: self]; + + if ((ret = sctp_recvv(_socket, &iov, 1, NULL, 0, &rcvinfo, &rcvinfoSize, + &infotype, 0)) < 0) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: _OFSocketErrNo()]; + + if (streamID != NULL) { + if (infotype == SCTP_RECVV_RCVINFO && + rcvinfoSize >= (socklen_t)sizeof(rcvinfo)) + *streamID = rcvinfo.rcv_sid; + else + *streamID = 0; + } + + if (PPID != NULL) { + if (infotype == SCTP_RECVV_RCVINFO && + rcvinfoSize >= (socklen_t)sizeof(rcvinfo)) + *PPID = rcvinfo.rcv_ppid; + else + *PPID = 0; + } + + if (flags != NULL) { + *flags = 0; + + if (infotype == SCTP_RECVV_RCVINFO && + rcvinfoSize >= (socklen_t)sizeof(rcvinfo) && + rcvinfo.rcv_flags & SCTP_UNORDERED) + *flags |= OFSCTPMessageUnordered; + } + + return ret; +} + +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length +{ + [self asyncReceiveWithInfoIntoBuffer: buffer + length: length + runLoopMode: OFDefaultRunLoopMode]; +} + +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (OFRunLoopMode)runLoopMode +{ + [OFRunLoop of_addAsyncReceiveForSCTPSocket: self + buffer: buffer + length: length + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + block: (OFSCTPSocketAsyncReceiveBlock)block +{ + [self asyncReceiveWithInfoIntoBuffer: buffer + length: length + runLoopMode: OFDefaultRunLoopMode + block: block]; +} + +- (void) + asyncReceiveWithInfoIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncReceiveBlock)block +{ + [OFRunLoop of_addAsyncReceiveForSCTPSocket: self + buffer: buffer + length: length + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)sendBuffer: (const void *)buffer length: (size_t)length +{ + [self sendBuffer: buffer length: length streamID: 0 PPID: 0 flags: 0]; +} + +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags +{ + ssize_t bytesWritten; + struct iovec iov = { + .iov_base = (void *)buffer, + .iov_len = length + }; + struct sctp_sndinfo sndinfo = { + .snd_sid = streamID, + .snd_ppid = PPID, + .snd_flags = + ((flags & OFSCTPMessageUnordered) ? SCTP_UNORDERED : 0), + }; + + if (_socket == OFInvalidSocketHandle) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (length > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = sctp_sendv(_socket, &iov, 1, NULL, 0, &sndinfo, + (socklen_t)sizeof(sndinfo), SCTP_SENDV_SNDINFO, 0)) < 0) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: _OFSocketErrNo()]; + + if ((size_t)bytesWritten != length) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: bytesWritten + errNo: 0]; +} + +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags +{ + [self asyncSendData: data + streamID: streamID + PPID: PPID + flags: flags + runLoopMode: OFDefaultRunLoopMode]; +} + +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + runLoopMode: (OFRunLoopMode)runLoopMode +{ + [OFRunLoop of_addAsyncSendForSCTPSocket: self + data: data + streamID: streamID + PPID: PPID + flags: flags + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + block: (OFSCTPSocketAsyncSendDataBlock)block +{ + [self asyncSendData: data + streamID: streamID + PPID: PPID + flags: flags + runLoopMode: OFDefaultRunLoopMode + block: block]; +} + +- (void)asyncSendData: (OFData *)data + streamID: (uint16_t)streamID + PPID: (uint32_t)PPID + flags: (OFSCTPMessageFlags)flags + runLoopMode: (OFRunLoopMode)runLoopMode + block: (OFSCTPSocketAsyncSendDataBlock)block +{ + [OFRunLoop of_addAsyncSendForSCTPSocket: self + data: data + streamID: streamID + PPID: PPID + flags: flags + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)setCanDelaySendingMessages: (bool)canDelaySendingMessages +{ + int v = !canDelaySendingMessages; + + if (setsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, (socklen_t)sizeof(v)) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: _OFSocketErrNo()]; +} + +- (bool)canDelaySendingMessages +{ + int v; + socklen_t len = sizeof(v); + + if (getsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, &len) != 0 || len != sizeof(v)) + @throw [OFGetOptionFailedException + exceptionWithObject: self + errNo: _OFSocketErrNo()]; + + return !v; +} +@end Index: src/OFSocket.h ================================================================== --- src/OFSocket.h +++ src/OFSocket.h @@ -36,10 +36,13 @@ #ifdef OF_HAVE_NETINET_IN_H # include #endif #ifdef OF_HAVE_NETINET_TCP_H # include +#endif +#ifdef OF_HAVE_NETINET_SCTP_H +# include #endif #ifdef OF_HAVE_SYS_UN_H # include #endif #ifdef OF_HAVE_AFUNIX_H Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -88,10 +88,13 @@ # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" +# ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +# endif # ifdef OF_HAVE_UNIX_SOCKETS # import "OFUNIXDatagramSocket.h" # import "OFUNIXStreamSocket.h" # endif # ifdef OF_HAVE_IPX Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -18,10 +18,11 @@ #undef OF_HAVE_LIMITS_H #undef OF_HAVE_LINK #undef OF_HAVE_NETATALK_AT_H #undef OF_HAVE_NETAT_APPLETALK_H #undef OF_HAVE_NETINET_IN_H +#undef OF_HAVE_NETINET_SCTP_H #undef OF_HAVE_NETINET_TCP_H #undef OF_HAVE_NETIPX_IPX_H #undef OF_HAVE_OSATOMIC #undef OF_HAVE_OSATOMIC_64 #undef OF_HAVE_PIPE Index: src/test/OTAssert.h ================================================================== --- src/test/OTAssert.h +++ src/test/OTAssert.h @@ -42,21 +42,21 @@ * @param condition The condition to check * @param ... An optional format string to print if the assertion failed, * followed by optional arguments */ #define OTAssertTrue(condition, ...) \ - OTAssert(condition == true, ## __VA_ARGS__) + OTAssert((condition) == true, ## __VA_ARGS__) /** * @brief Asserts that the specified condition is false. * * @param condition The condition to check * @param ... An optional format string to print if the assertion failed, * followed by optional arguments */ #define OTAssertFalse(condition, ...) \ - OTAssert(condition == false, ## __VA_ARGS__) + OTAssert((condition) == false, ## __VA_ARGS__) /** * @brief Asserts that the two values are equal. * * @param a The value to check Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -90,15 +90,17 @@ OFSocketTests.m \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ ${USE_SRCS_APPLETALK} \ ${USE_SRCS_IPX} \ + ${USE_SRCS_SCTP} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_APPLETALK = OFDDPSocketTests.m SRCS_IPX = OFIPXSocketTests.m \ OFSPXSocketTests.m \ OFSPXStreamSocketTests.m +SRCS_SCTP = OFSCTPSocketTests.m SRCS_UNIX_SOCKETS = OFUNIXDatagramSocketTests.m \ OFUNIXStreamSocketTests.m SRCS_SUBPROCESSES = OFSubprocessTests.m SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m ADDED tests/OFSCTPSocketTests.m Index: tests/OFSCTPSocketTests.m ================================================================== --- tests/OFSCTPSocketTests.m +++ tests/OFSCTPSocketTests.m @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#include +#include + +#import "ObjFW.h" +#import "ObjFWTest.h" + +@interface OFSCTPSocketTests: OTTestCase +@end + +@implementation OFSCTPSocketTests +- (void)testSCTPSocket +{ + OFSCTPSocket *server, *client, *accepted; + OFSocketAddress address; + char buffer[6]; + uint16_t streamID; + uint32_t PPID; + OFSCTPMessageFlags flags; + + server = [OFSCTPSocket socket]; + client = [OFSCTPSocket socket]; + + @try { + address = [server bindToHost: @"127.0.0.1" port: 0]; + } @catch (OFBindSocketFailedException *e) { + switch (e.errNo) { + case EPROTONOSUPPORT: + OTSkip(@"SCTP unsupported"); + default: + @throw e; + } + } + + [server listen]; + + [client connectToHost: @"127.0.0.1" + port: OFSocketAddressIPPort(&address)]; + + accepted = [server accept]; + OTAssertEqualObjects(OFSocketAddressString(accepted.remoteAddress), + @"127.0.0.1"); + + [client sendBuffer: "Hello!" + length: 6 + streamID: 1 + PPID: 1234 + flags: OFSCTPMessageUnordered]; + + [accepted receiveIntoBuffer: buffer + length: 6 + streamID: &streamID + PPID: &PPID + flags: &flags]; + OTAssertEqual(memcmp(buffer, "Hello!", 6), 0); + OTAssertEqual(streamID, 1); + OTAssertEqual(PPID, 1234); + OTAssertTrue(flags & OFSCTPMessageUnordered); +} +@end