Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -216,10 +216,11 @@ SRCS_FILES += OFFileURLHandler.m \ OFINIFileSettings.m SRCS_SOCKETS += OFDNSResolverSettings.m \ OFHTTPURLHandler.m \ OFHostAddressResolver.m \ + OFIPSocketAsyncConnector.m \ OFKernelEventObserver.m \ ${OF_EPOLL_KERNEL_EVENT_OBSERVER_M} \ ${OF_KQUEUE_KERNEL_EVENT_OBSERVER_M} \ ${OF_POLL_KERNEL_EVENT_OBSERVER_M} \ ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} \ ADDED src/OFIPSocketAsyncConnector.h Index: src/OFIPSocketAsyncConnector.h ================================================================== --- src/OFIPSocketAsyncConnector.h +++ src/OFIPSocketAsyncConnector.h @@ -0,0 +1,55 @@ +/* + * 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 "OFDNSResolver.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +OF_ASSUME_NONNULL_BEGIN + +@protocol OFIPSocketAsyncConnecting +- (bool)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface 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: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (nullable id)delegate + block: (nullable id)block; +- (void)didConnect; +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFIPSocketAsyncConnector.m Index: src/OFIPSocketAsyncConnector.m ================================================================== --- src/OFIPSocketAsyncConnector.m +++ src/OFIPSocketAsyncConnector.m @@ -0,0 +1,255 @@ +/* + * 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 "OFIPSocketAsyncConnector.h" +#import "OFData.h" +#import "OFTCPSocket.h" +#import "OFThread.h" +#import "OFTimer.h" + +#import "OFConnectionFailedException.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 setBlocking: true]; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + if ([_socket isKindOfClass: [OFTCPSocket class]]) + ((of_tcp_socket_async_connect_block_t)_block)( + _exception); + else + OF_ENSURE(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 [OFConnectionFailedException exceptionWithHost: _host + port: _port + socket: _socket + errNo: errNo]; +} + +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = *(const of_socket_address_t *) + [_socketAddresses itemAtIndex: _socketAddressesIndex++]; + int errNo; + + of_socket_address_set_port(&address, _port); + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException 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 setBlocking: true]; +#else + [_socket setBlocking: false]; +#endif + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } else { +#endif + [_socket of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException + 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 setBlocking: 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: (of_run_loop_mode_t)runLoopMode +{ + @try { + of_socket_address_t address = + of_socket_address_parse_ip(_host, _port); + + _socketAddresses = [[OFData alloc] + initWithItems: &address + itemSize: sizeof(address) + count: 1]; + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } @catch (OFInvalidFormatException *e) { + } + + [[OFThread DNSResolver] + asyncResolveAddressesForHost: _host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY + runLoopMode: runLoopMode + delegate: self]; +} +@end Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -30,26 +30,22 @@ #import "OFTCPSocket.h" #import "OFDNSResolver.h" #import "OFData.h" #import "OFDate.h" +#import "OFIPSocketAsyncConnector.h" #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFString.h" #import "OFTCPSocketSOCKS5Connector.h" #import "OFThread.h" -#import "OFTimer.h" #import "OFAlreadyConnectedException.h" #import "OFBindFailedException.h" -#import "OFConnectionFailedException.h" #import "OFGetOptionFailedException.h" -#import "OFInvalidFormatException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" -#import "OFOutOfMemoryException.h" -#import "OFOutOfRangeException.h" #import "OFSetOptionFailedException.h" #import "socket.h" #import "socket_helpers.h" @@ -59,280 +55,19 @@ Class of_tls_socket_class = Nil; static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; -@interface OFTCPSocket () -- (bool)of_createSocketForAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (void)of_closeSocket; -@end - -@interface OFTCPSocketAsyncConnectDelegate: OFObject -{ - OFTCPSocket *_socket; - OFString *_host; - uint16_t _port; - id _delegate; -#ifdef OF_HAVE_BLOCKS - of_tcp_socket_async_connect_block_t _block; -#endif - id _exception; - OFData *_socketAddresses; - size_t _socketAddressesIndex; -} - -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate -#ifdef OF_HAVE_BLOCKS - block: (of_tcp_socket_async_connect_block_t)block -#endif -; -- (void)didConnect; -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@interface OFTCPSocket () @end @interface OFTCPSocketConnectDelegate: OFObject { @public bool _done; id _exception; } -@end - -@implementation OFTCPSocketAsyncConnectDelegate -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate -#ifdef OF_HAVE_BLOCKS - block: (of_tcp_socket_async_connect_block_t)block -#endif -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _delegate = [delegate retain]; -#ifdef OF_HAVE_BLOCKS - _block = [block copy]; -#endif - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_socket release]; - [_host release]; - [_delegate release]; -#ifdef OF_HAVE_BLOCKS - [_block release]; -#endif - [_exception release]; - [_socketAddresses release]; - - [super dealloc]; -} - -- (void)didConnect -{ - if (_exception == nil) - _socket.blocking = true; - -#ifdef OF_HAVE_BLOCKS - if (_block != NULL) - _block(_exception); - 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 [OFConnectionFailedException exceptionWithHost: _host - port: _port - socket: _socket - errNo: errNo]; -} - -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - of_socket_address_t address = *(const of_socket_address_t *) - [_socketAddresses itemAtIndex: _socketAddressesIndex++]; - int errNo; - - of_socket_address_set_port(&address, _port); - - if (![_socket of_createSocketForAddress: &address - errNo: &errNo]) { - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException 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.blocking = true; -#else - _socket.blocking = false; -#endif - - if (![_socket of_connectSocketToAddress: &address - errNo: &errNo]) { -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - if (errNo == EINPROGRESS) { - [OFRunLoop of_addAsyncConnectForSocket: _socket - mode: runLoopMode - delegate: self]; - return; - } else { -#endif - [_socket of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException - 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.blocking = 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: (of_run_loop_mode_t)runLoopMode -{ - @try { - of_socket_address_t address = - of_socket_address_parse_ip(_host, _port); - - _socketAddresses = [[OFData alloc] - initWithItems: &address - itemSize: sizeof(address) - count: 1]; - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } @catch (OFInvalidFormatException *e) { - } - - [[OFThread DNSResolver] - asyncResolveAddressesForHost: _host - addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY - runLoopMode: runLoopMode - delegate: self]; -} @end @implementation OFTCPSocketConnectDelegate - (void)dealloc { @@ -489,10 +224,13 @@ runLoopMode: (of_run_loop_mode_t)runLoopMode { void *pool = objc_autoreleasePoolPush(); id delegate; + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + if (_SOCKS5Host != nil) { delegate = [[[OFTCPSocketSOCKS5Connector alloc] initWithSocket: self host: host port: port @@ -504,18 +242,16 @@ host = _SOCKS5Host; port = _SOCKS5Port; } else delegate = _delegate; - [[[[OFTCPSocketAsyncConnectDelegate alloc] + [[[[OFIPSocketAsyncConnector alloc] initWithSocket: self host: host port: port delegate: delegate -#ifdef OF_HAVE_BLOCKS block: NULL -#endif ] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } @@ -536,10 +272,13 @@ block: (of_tcp_socket_async_connect_block_t)block { void *pool = objc_autoreleasePoolPush(); id delegate = nil; + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + if (_SOCKS5Host != nil) { delegate = [[[OFTCPSocketSOCKS5Connector alloc] initWithSocket: self host: host port: port @@ -547,11 +286,11 @@ block: block] autorelease]; host = _SOCKS5Host; port = _SOCKS5Port; } - [[[[OFTCPSocketAsyncConnectDelegate alloc] + [[[[OFIPSocketAsyncConnector alloc] initWithSocket: self host: host port: port delegate: delegate block: (delegate == nil ? block : NULL)] autorelease]