Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -211,15 +211,15 @@ ${LIBBASES_M} \ ${RUNTIME_AUTORELEASE_M} \ ${RUNTIME_INSTANCE_M} \ ${UNICODE_M} SRCS_FILES += OFFileURIHandler.m -SRCS_SOCKETS += OFDNSResolverSettings.m \ +SRCS_SOCKETS += OFAsyncIPSocketConnector.m \ + OFDNSResolverSettings.m \ ${OF_EPOLL_KERNEL_EVENT_OBSERVER_M} \ OFHTTPURIHandler.m \ OFHostAddressResolver.m \ - OFIPSocketAsyncConnector.m \ OFKernelEventObserver.m \ ${OF_KQUEUE_KERNEL_EVENT_OBSERVER_M} \ ${OF_POLL_KERNEL_EVENT_OBSERVER_M} \ ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} \ OFTCPSocketSOCKS5Connector.m ADDED src/OFAsyncIPSocketConnector.h Index: src/OFAsyncIPSocketConnector.h ================================================================== --- src/OFAsyncIPSocketConnector.h +++ src/OFAsyncIPSocketConnector.h @@ -0,0 +1,53 @@ +/* + * 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 "OFDNSResolver.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +OF_ASSUME_NONNULL_BEGIN + +@protocol OFAsyncIPSocketConnecting +- (bool)of_createSocketForAddress: (const OFSocketAddress *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const OFSocketAddress *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface OFAsyncIPSocketConnector: OFObject +{ + id _socket; + OFString *_host; + uint16_t _port; + id _Nullable _delegate; + id _Nullable _block; + id _Nullable _exception; + OFData *_Nullable _socketAddresses; + size_t _socketAddressesIndex; +} + +- (instancetype)initWithSocket: (id)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (nullable id)delegate + block: (nullable id)block; +- (void)didConnect; +- (void)tryNextAddressWithRunLoopMode: (OFRunLoopMode)runLoopMode; +- (void)startWithRunLoopMode: (OFRunLoopMode)runLoopMode; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFAsyncIPSocketConnector.m Index: src/OFAsyncIPSocketConnector.m ================================================================== --- src/OFAsyncIPSocketConnector.m +++ src/OFAsyncIPSocketConnector.m @@ -0,0 +1,251 @@ +/* + * 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 "OFAsyncIPSocketConnector.h" +#import "OFData.h" +#import "OFTCPSocket.h" +#import "OFThread.h" +#import "OFTimer.h" + +#import "OFConnectIPSocketFailedException.h" +#import "OFInvalidFormatException.h" + +@implementation OFAsyncIPSocketConnector +- (instancetype)initWithSocket: (id)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (id)delegate + block: (id)block +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _delegate = [delegate retain]; + _block = [block copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_socket release]; + [_host release]; + [_delegate release]; + [_block release]; + [_exception release]; + [_socketAddresses release]; + + [super dealloc]; +} + +- (void)didConnect +{ + if (_exception == nil) + [_socket setCanBlock: true]; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + if ([_socket isKindOfClass: [OFTCPSocket class]]) + ((OFTCPSocketAsyncConnectBlock)_block)(_exception); + else + OFEnsure(0); + } else { +#endif + if ([_delegate respondsToSelector: + @selector(socket:didConnectToHost:port:exception:)]) + [_delegate socket: _socket + didConnectToHost: _host + port: _port + exception: _exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (void)of_socketDidConnect: (id)sock exception: (id)exception +{ + if (exception != nil) { + /* + * self might be retained only by the pending async requests, + * which we're about to cancel. + */ + [[self retain] autorelease]; + + [sock cancelAsyncRequests]; + [sock of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [exception retain]; + [self didConnect]; + } else { + /* + * We must not call it before returning, as otherwise + * the new socket would be removed from the queue upon + * return. + */ + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + SEL selector = + @selector(tryNextAddressWithRunLoopMode:); + OFTimer *timer = [OFTimer + timerWithTimeInterval: 0 + target: self + selector: selector + object: runLoop.currentMode + repeats: false]; + [runLoop addTimer: timer forMode: runLoop.currentMode]; + } + + return; + } + + [self didConnect]; +} + +- (id)of_connectionFailedExceptionForErrNo: (int)errNo +{ + return [OFConnectIPSocketFailedException exceptionWithHost: _host + port: _port + socket: _socket + errNo: errNo]; +} + +- (void)tryNextAddressWithRunLoopMode: (OFRunLoopMode)runLoopMode +{ + OFSocketAddress address = *(const OFSocketAddress *) + [_socketAddresses itemAtIndex: _socketAddressesIndex++]; + int errNo; + + OFSocketAddressSetIPPort(&address, _port); + + if (![_socket of_createSocketForAddress: &address errNo: &errNo]) { + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectIPSocketFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + /* + * On Wii and 3DS, connect() fails if non-blocking is enabled. + * + * Additionally, on Wii, there is no getsockopt(), so it would not be + * possible to get the error (or success) after connecting anyway. + * + * So for now, connecting is blocking on Wii and 3DS. + * + * FIXME: Use a different thread as a work around. + */ + [_socket setCanBlock: true]; +#else + [_socket setCanBlock: false]; +#endif + + if (![_socket of_connectSocketToAddress: &address errNo: &errNo]) { +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) +# ifdef OF_WINDOWS + if (errNo == EINPROGRESS || errNo == EWOULDBLOCK) { +# else + if (errNo == EINPROGRESS) { +# endif + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } else { +#endif + [_socket of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectIPSocketFailedException + alloc] initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + } +#endif + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + [_socket setCanBlock: false]; +#endif + + [self didConnect]; +} + +- (void)resolver: (OFDNSResolver *)resolver + didResolveHost: (OFString *)host + addresses: (OFData *)addresses + exception: (id)exception +{ + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return; + } + + _socketAddresses = [addresses copy]; + + [self tryNextAddressWithRunLoopMode: + [OFRunLoop currentRunLoop].currentMode]; +} + +- (void)startWithRunLoopMode: (OFRunLoopMode)runLoopMode +{ + @try { + OFSocketAddress address = OFSocketAddressParseIP(_host, _port); + + _socketAddresses = [[OFData alloc] + initWithItems: &address + count: 1 + itemSize: sizeof(address)]; + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } @catch (OFInvalidFormatException *e) { + } + + [[OFThread DNSResolver] + asyncResolveAddressesForHost: _host + addressFamily: OFSocketAddressFamilyAny + runLoopMode: runLoopMode + delegate: self]; +} +@end DELETED src/OFIPSocketAsyncConnector.h Index: src/OFIPSocketAsyncConnector.h ================================================================== --- src/OFIPSocketAsyncConnector.h +++ src/OFIPSocketAsyncConnector.h @@ -1,53 +0,0 @@ -/* - * 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 "OFDNSResolver.h" -#import "OFRunLoop.h" -#import "OFRunLoop+Private.h" - -OF_ASSUME_NONNULL_BEGIN - -@protocol OFIPSocketAsyncConnecting -- (bool)of_createSocketForAddress: (const OFSocketAddress *)address - errNo: (int *)errNo; -- (bool)of_connectSocketToAddress: (const OFSocketAddress *)address - errNo: (int *)errNo; -- (void)of_closeSocket; -@end - -@interface OFIPSocketAsyncConnector: OFObject -{ - id _socket; - OFString *_host; - uint16_t _port; - id _Nullable _delegate; - id _Nullable _block; - id _Nullable _exception; - OFData *_Nullable _socketAddresses; - size_t _socketAddressesIndex; -} - -- (instancetype)initWithSocket: (id)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (nullable id)delegate - block: (nullable id)block; -- (void)didConnect; -- (void)tryNextAddressWithRunLoopMode: (OFRunLoopMode)runLoopMode; -- (void)startWithRunLoopMode: (OFRunLoopMode)runLoopMode; -@end - -OF_ASSUME_NONNULL_END DELETED src/OFIPSocketAsyncConnector.m Index: src/OFIPSocketAsyncConnector.m ================================================================== --- src/OFIPSocketAsyncConnector.m +++ src/OFIPSocketAsyncConnector.m @@ -1,251 +0,0 @@ -/* - * 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 "OFIPSocketAsyncConnector.h" -#import "OFData.h" -#import "OFTCPSocket.h" -#import "OFThread.h" -#import "OFTimer.h" - -#import "OFConnectIPSocketFailedException.h" -#import "OFInvalidFormatException.h" - -@implementation OFIPSocketAsyncConnector -- (instancetype)initWithSocket: (id)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id)delegate - block: (id)block -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _delegate = [delegate retain]; - _block = [block copy]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_socket release]; - [_host release]; - [_delegate release]; - [_block release]; - [_exception release]; - [_socketAddresses release]; - - [super dealloc]; -} - -- (void)didConnect -{ - if (_exception == nil) - [_socket setCanBlock: true]; - -#ifdef OF_HAVE_BLOCKS - if (_block != NULL) { - if ([_socket isKindOfClass: [OFTCPSocket class]]) - ((OFTCPSocketAsyncConnectBlock)_block)(_exception); - else - OFEnsure(0); - } else { -#endif - if ([_delegate respondsToSelector: - @selector(socket:didConnectToHost:port:exception:)]) - [_delegate socket: _socket - didConnectToHost: _host - port: _port - exception: _exception]; -#ifdef OF_HAVE_BLOCKS - } -#endif -} - -- (void)of_socketDidConnect: (id)sock exception: (id)exception -{ - if (exception != nil) { - /* - * self might be retained only by the pending async requests, - * which we're about to cancel. - */ - [[self retain] autorelease]; - - [sock cancelAsyncRequests]; - [sock of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [exception retain]; - [self didConnect]; - } else { - /* - * We must not call it before returning, as otherwise - * the new socket would be removed from the queue upon - * return. - */ - OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; - SEL selector = - @selector(tryNextAddressWithRunLoopMode:); - OFTimer *timer = [OFTimer - timerWithTimeInterval: 0 - target: self - selector: selector - object: runLoop.currentMode - repeats: false]; - [runLoop addTimer: timer forMode: runLoop.currentMode]; - } - - return; - } - - [self didConnect]; -} - -- (id)of_connectionFailedExceptionForErrNo: (int)errNo -{ - return [OFConnectIPSocketFailedException exceptionWithHost: _host - port: _port - socket: _socket - errNo: errNo]; -} - -- (void)tryNextAddressWithRunLoopMode: (OFRunLoopMode)runLoopMode -{ - OFSocketAddress address = *(const OFSocketAddress *) - [_socketAddresses itemAtIndex: _socketAddressesIndex++]; - int errNo; - - OFSocketAddressSetIPPort(&address, _port); - - if (![_socket of_createSocketForAddress: &address errNo: &errNo]) { - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectIPSocketFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - /* - * On Wii and 3DS, connect() fails if non-blocking is enabled. - * - * Additionally, on Wii, there is no getsockopt(), so it would not be - * possible to get the error (or success) after connecting anyway. - * - * So for now, connecting is blocking on Wii and 3DS. - * - * FIXME: Use a different thread as a work around. - */ - [_socket setCanBlock: true]; -#else - [_socket setCanBlock: false]; -#endif - - if (![_socket of_connectSocketToAddress: &address errNo: &errNo]) { -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) -# ifdef OF_WINDOWS - if (errNo == EINPROGRESS || errNo == EWOULDBLOCK) { -# else - if (errNo == EINPROGRESS) { -# endif - [OFRunLoop of_addAsyncConnectForSocket: _socket - mode: runLoopMode - delegate: self]; - return; - } else { -#endif - [_socket of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectIPSocketFailedException - alloc] initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - } -#endif - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - [_socket setCanBlock: false]; -#endif - - [self didConnect]; -} - -- (void)resolver: (OFDNSResolver *)resolver - didResolveHost: (OFString *)host - addresses: (OFData *)addresses - exception: (id)exception -{ - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return; - } - - _socketAddresses = [addresses copy]; - - [self tryNextAddressWithRunLoopMode: - [OFRunLoop currentRunLoop].currentMode]; -} - -- (void)startWithRunLoopMode: (OFRunLoopMode)runLoopMode -{ - @try { - OFSocketAddress address = OFSocketAddressParseIP(_host, _port); - - _socketAddresses = [[OFData alloc] - initWithItems: &address - count: 1 - itemSize: sizeof(address)]; - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } @catch (OFInvalidFormatException *e) { - } - - [[OFThread DNSResolver] - asyncResolveAddressesForHost: _host - addressFamily: OFSocketAddressFamilyAny - runLoopMode: runLoopMode - delegate: self]; -} -@end Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -29,14 +29,14 @@ #ifdef HAVE_FCNTL_H # include #endif #import "OFTCPSocket.h" +#import "OFAsyncIPSocketConnector.h" #import "OFDNSResolver.h" #import "OFData.h" #import "OFDate.h" -#import "OFIPSocketAsyncConnector.h" #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFSocket.h" #import "OFSocket+Private.h" #import "OFString.h" @@ -54,11 +54,11 @@ @"OFTCPSocketConnectRunLoopMode"; static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; -@interface OFTCPSocket () +@interface OFTCPSocket () @end @interface OFTCPSocketConnectDelegate: OFObject { @public @@ -234,11 +234,11 @@ host = _SOCKS5Host; port = _SOCKS5Port; } else delegate = _delegate; - [[[[OFIPSocketAsyncConnector alloc] + [[[[OFAsyncIPSocketConnector alloc] initWithSocket: self host: host port: port delegate: delegate block: NULL @@ -275,11 +275,11 @@ block: block] autorelease]; host = _SOCKS5Host; port = _SOCKS5Port; } - [[[[OFIPSocketAsyncConnector alloc] + [[[[OFAsyncIPSocketConnector alloc] initWithSocket: self host: host port: port delegate: delegate block: (delegate == nil ? block : NULL)] autorelease]