From 85f50867aeafafddf06db096ddf121768895e92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A5=9D=E5=8F=91=E5=86=AC?= Date: Fri, 21 Jul 2023 10:54:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0socket=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ifish/AsyncSocket/GCD/GCDAsyncSocket.h | 428 ++- Ifish/AsyncSocket/GCD/GCDAsyncSocket.m | 3140 ++++++++++++++------- Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h | 231 +- Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.m | 763 +++-- 4 files changed, 3047 insertions(+), 1515 deletions(-) mode change 100755 => 100644 Ifish/AsyncSocket/GCD/GCDAsyncSocket.h mode change 100755 => 100644 Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h diff --git a/Ifish/AsyncSocket/GCD/GCDAsyncSocket.h b/Ifish/AsyncSocket/GCD/GCDAsyncSocket.h old mode 100755 new mode 100644 index cf9927f..c339f8a --- a/Ifish/AsyncSocket/GCD/GCDAsyncSocket.h +++ b/Ifish/AsyncSocket/GCD/GCDAsyncSocket.h @@ -12,52 +12,16 @@ #import #import #import +#import + +#include // AF_INET, AF_INET6 @class GCDAsyncReadPacket; @class GCDAsyncWritePacket; @class GCDAsyncSocketPreBuffer; +@protocol GCDAsyncSocketDelegate; -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0 supported - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 // iOS 5.0 supported and required - - #define IS_SECURE_TRANSPORT_AVAILABLE YES - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0 - - #else // iOS 5.0 supported but not required - - #ifndef NSFoundationVersionNumber_iPhoneOS_5_0 - #define NSFoundationVersionNumber_iPhoneOS_5_0 881.00 - #endif - - #define IS_SECURE_TRANSPORT_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_5_0) - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1 - - #endif - - #else // iOS 5.0 not supported - - #define IS_SECURE_TRANSPORT_AVAILABLE NO - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 0 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1 - - #endif - -#else - - // Compiling for Mac OS X - - #define IS_SECURE_TRANSPORT_AVAILABLE YES - #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1 - #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0 - -#endif +NS_ASSUME_NONNULL_BEGIN extern NSString *const GCDAsyncSocketException; extern NSString *const GCDAsyncSocketErrorDomain; @@ -65,18 +29,28 @@ extern NSString *const GCDAsyncSocketErrorDomain; extern NSString *const GCDAsyncSocketQueueName; extern NSString *const GCDAsyncSocketThreadName; -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -extern NSString *const GCDAsyncSocketSSLCipherSuites; +extern NSString *const GCDAsyncSocketManuallyEvaluateTrust; #if TARGET_OS_IPHONE +extern NSString *const GCDAsyncSocketUseCFStreamForTLS; +#endif +#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName +#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates +#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer +extern NSString *const GCDAsyncSocketSSLPeerID; extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -#else +extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart; +extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord; +extern NSString *const GCDAsyncSocketSSLCipherSuites; +extern NSString *const GCDAsyncSocketSSLALPN; +#if !TARGET_OS_IPHONE extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; #endif -#endif -enum GCDAsyncSocketError -{ +#define GCDAsyncSocketLoggingContext 65535 + + +typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) { GCDAsyncSocketNoError = 0, // Never used GCDAsyncSocketBadConfigError, // Invalid configuration GCDAsyncSocketBadParamError, // Invalid parameter was passed @@ -87,12 +61,12 @@ enum GCDAsyncSocketError GCDAsyncSocketClosedError, // The remote peer closed the connection GCDAsyncSocketOtherError, // Description provided in userInfo }; -typedef enum GCDAsyncSocketError GCDAsyncSocketError; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + @interface GCDAsyncSocket : NSObject /** @@ -111,24 +85,39 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * * The delegate queue and socket queue can optionally be the same. **/ -- (id)init; -- (id)initWithSocketQueue:(dispatch_queue_t)sq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq; +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; + +/** + * Create GCDAsyncSocket from already connect BSD socket file descriptor +**/ ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error; + ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error; + ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error; #pragma mark Configuration -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; +@property (atomic, weak, readwrite, nullable) id delegate; +#if OS_OBJECT_USE_OBJC +@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue; +#else +@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue; +#endif -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue; +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; +/** + * If you are setting the delegate to nil within the delegate's dealloc method, + * you may need to use the synchronous versions below. +**/ +- (void)synchronouslySetDelegate:(nullable id)delegate; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; /** * By default, both IPv4 and IPv6 are enabled. @@ -142,21 +131,25 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen. * By default, the preferred protocol is IPv4, but may be configured as desired. **/ -- (BOOL)isIPv4Enabled; -- (void)setIPv4Enabled:(BOOL)flag; -- (BOOL)isIPv6Enabled; -- (void)setIPv6Enabled:(BOOL)flag; +@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled; +@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled; -- (BOOL)isIPv4PreferredOverIPv6; -- (void)setPreferIPv4OverIPv6:(BOOL)flag; +@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6; + +/** + * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555 + * this is the delay between connecting to the preferred protocol and the fallback protocol. + * + * Defaults to 300ms. +**/ +@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay; /** * User data allows you to associate arbitrary information with the socket. * This data is not used internally by socket in any way. **/ -- (id)userData; -- (void)setUserData:(id)arbitraryUserData; +@property (atomic, strong, readwrite, nullable) id userData; #pragma mark Accepting @@ -185,7 +178,16 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. **/ -- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; +- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; + +/** + * Tells the socket to begin listening and accepting connections on the unix domain at the given url. + * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, + * and the socket:didAcceptNewSocket: delegate method will be invoked. + * + * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) + **/ +- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; #pragma mark Connecting @@ -241,7 +243,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port - viaInterface:(NSString *)interface + viaInterface:(nullable NSString *)interface withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; @@ -253,7 +255,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; * - * This method invokes connectToAdd + * This method invokes connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr. **/ - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; @@ -299,9 +301,19 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * This feature is here for networking professionals using very advanced techniques. **/ - (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterface:(NSString *)interface + viaInterface:(nullable NSString *)interface withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; +/** + * Connects to the unix domain socket at the given url, using the specified timeout. + */ +- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; + +/** + * Iterates over the given NetService's addresses in order, and invokes connectToAddress:error:. Stops at the + * first invocation that succeeds and returns YES; otherwise returns NO. + */ +- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr; #pragma mark Disconnecting @@ -352,45 +364,49 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * Returns whether the socket is disconnected or connected. * * A disconnected socket may be recycled. - * That is, it can used again for connecting or listening. + * That is, it can be used again for connecting or listening. * * If a socket is in the process of connecting, it may be neither disconnected nor connected. **/ -- (BOOL)isDisconnected; -- (BOOL)isConnected; +@property (atomic, readonly) BOOL isDisconnected; +@property (atomic, readonly) BOOL isConnected; /** * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. * The host will be an IP address. **/ -- (NSString *)connectedHost; -- (uint16_t)connectedPort; +@property (atomic, readonly, nullable) NSString *connectedHost; +@property (atomic, readonly) uint16_t connectedPort; +@property (atomic, readonly, nullable) NSURL *connectedUrl; -- (NSString *)localHost; -- (uint16_t)localPort; +@property (atomic, readonly, nullable) NSString *localHost; +@property (atomic, readonly) uint16_t localPort; /** * Returns the local or remote address to which this socket is connected, * specified as a sockaddr structure wrapped in a NSData object. * - * See also the connectedHost, connectedPort, localHost and localPort methods. + * @seealso connectedHost + * @seealso connectedPort + * @seealso localHost + * @seealso localPort **/ -- (NSData *)connectedAddress; -- (NSData *)localAddress; +@property (atomic, readonly, nullable) NSData *connectedAddress; +@property (atomic, readonly, nullable) NSData *localAddress; /** * Returns whether the socket is IPv4 or IPv6. * An accepting socket may be both. **/ -- (BOOL)isIPv4; -- (BOOL)isIPv6; +@property (atomic, readonly) BOOL isIPv4; +@property (atomic, readonly) BOOL isIPv6; /** * Returns whether or not the socket has been secured via SSL/TLS. * * See also the startTLS method. **/ -- (BOOL)isSecure; +@property (atomic, readonly) BOOL isSecure; #pragma mark Reading @@ -420,7 +436,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * The given buffer will automatically be increased in size if needed. * * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, the socket will create a buffer for you. + * If the buffer is nil, the socket will create a buffer for you. * * If the bufferOffset is greater than the length of the given buffer, * the method will do nothing, and the delegate will not be called. @@ -431,7 +447,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. **/ - (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer + buffer:(nullable NSMutableData *)buffer bufferOffset:(NSUInteger)offset tag:(long)tag; @@ -442,7 +458,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * A maximum of length bytes will be read. * * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. + * If the buffer is nil, a buffer will automatically be created for you. * If maxLength is zero, no length restriction is enforced. * * If the bufferOffset is greater than the length of the given buffer, @@ -454,7 +470,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. **/ - (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer + buffer:(nullable NSMutableData *)buffer bufferOffset:(NSUInteger)offset maxLength:(NSUInteger)length tag:(long)tag; @@ -474,7 +490,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * The given buffer will automatically be increased in size if needed. * * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. + * If the buffer is nil, a buffer will automatically be created for you. * * If the length is 0, this method does nothing and the delegate is not called. * If the bufferOffset is greater than the length of the given buffer, @@ -487,7 +503,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer + buffer:(nullable NSMutableData *)buffer bufferOffset:(NSUInteger)offset tag:(long)tag; @@ -512,7 +528,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * For performance reasons, the socket will retain it, not copy it. * So if it is immutable, don't modify it while the socket is using it. **/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; +- (void)readDataToData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; /** * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. @@ -520,7 +536,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * The given buffer will automatically be increased in size if needed. * * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. + * If the buffer is nil, a buffer will automatically be created for you. * * If the bufferOffset is greater than the length of the given buffer, * the method will do nothing (except maybe print a warning), and the delegate will not be called. @@ -545,7 +561,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer + buffer:(nullable NSMutableData *)buffer bufferOffset:(NSUInteger)offset tag:(long)tag; @@ -585,7 +601,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * The given buffer will automatically be increased in size if needed. * * If the timeout value is negative, the read operation will not use a timeout. - * If the buffer if nil, a buffer will automatically be created for you. + * If the buffer is nil, a buffer will automatically be created for you. * * If maxLength is zero, no length restriction is enforced. * Otherwise if maxLength bytes are read without completing the read, @@ -617,7 +633,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer + buffer:(nullable NSMutableData *)buffer bufferOffset:(NSUInteger)offset maxLength:(NSUInteger)length tag:(long)tag; @@ -626,7 +642,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check). * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. **/ -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; +- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; #pragma mark Writing @@ -647,13 +663,13 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. **/ -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; +- (void)writeData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; /** * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check). * The parameters "tag", "done" and "total" will be filled in if they aren't NULL. **/ -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr; +- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr; #pragma mark Security @@ -664,35 +680,116 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing * the upgrade to TLS at the same time, without having to wait for the write to finish. * Any reads or writes scheduled after this method is called will occur over the secured connection. + * + * ==== The available TOP-LEVEL KEYS are: * - * The possible keys and values for the TLS settings are well documented. - * Standard keys are: - * - * - kCFStreamSSLLevel - * - kCFStreamSSLAllowsExpiredCertificates - * - kCFStreamSSLAllowsExpiredRoots - * - kCFStreamSSLAllowsAnyRoot - * - kCFStreamSSLValidatesCertificateChain + * - GCDAsyncSocketManuallyEvaluateTrust + * The value must be of type NSNumber, encapsulating a BOOL value. + * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer. + * Instead it will pause at the moment evaulation would typically occur, + * and allow us to handle the security evaluation however we see fit. + * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef. + * + * Note that if you set this option, then all other configuration keys are ignored. + * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method. + * + * For more information on trust evaluation see: + * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation + * https://developer.apple.com/library/ios/technotes/tn2232/_index.html + * + * If unspecified, the default value is NO. + * + * - GCDAsyncSocketUseCFStreamForTLS (iOS only) + * The value must be of type NSNumber, encapsulating a BOOL value. + * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption. + * This gives us more control over the security protocol (many more configuration options), + * plus it allows us to optimize things like sys calls and buffer allocation. + * + * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption + * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket + * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property + * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method. + * + * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket, + * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty. + * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings. + * + * If unspecified, the default value is NO. + * + * ==== The available CONFIGURATION KEYS are: + * * - kCFStreamSSLPeerName + * The value must be of type NSString. + * It should match the name in the X.509 certificate given by the remote party. + * See Apple's documentation for SSLSetPeerDomainName. + * * - kCFStreamSSLCertificates + * The value must be of type NSArray. + * See Apple's documentation for SSLSetCertificate. + * * - kCFStreamSSLIsServer - * - * If SecureTransport is available on iOS: - * - * - GCDAsyncSocketSSLCipherSuites + * The value must be of type NSNumber, encapsulationg a BOOL value. + * See Apple's documentation for SSLCreateContext for iOS. + * This is optional for iOS. If not supplied, a NO value is the default. + * This is not needed for Mac OS X, and the value is ignored. + * + * - GCDAsyncSocketSSLPeerID + * The value must be of type NSData. + * You must set this value if you want to use TLS session resumption. + * See Apple's documentation for SSLSetPeerID. + * * - GCDAsyncSocketSSLProtocolVersionMin * - GCDAsyncSocketSSLProtocolVersionMax + * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value. + * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax. + * See also the SSLProtocol typedef. * - * If SecureTransport is available on Mac OS X: + * - GCDAsyncSocketSSLSessionOptionFalseStart + * The value must be of type NSNumber, encapsulating a BOOL value. + * See Apple's documentation for kSSLSessionOptionFalseStart. + * + * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord + * The value must be of type NSNumber, encapsulating a BOOL value. + * See Apple's documentation for kSSLSessionOptionSendOneByteRecord. * * - GCDAsyncSocketSSLCipherSuites - * - GCDAsyncSocketSSLDiffieHellmanParameters; + * The values must be of type NSArray. + * Each item within the array must be a NSNumber, encapsulating an SSLCipherSuite. + * See Apple's documentation for SSLSetEnabledCiphers. + * See also the SSLCipherSuite typedef. + * + * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only) + * The value must be of type NSData. + * See Apple's documentation for SSLSetDiffieHellmanParams. * + * ==== The following UNAVAILABLE KEYS are: (with throw an exception) * - * Please refer to Apple's documentation for associated values, as well as other possible keys. + * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsAnyRoot * + * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsExpiredRoots + * + * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetAllowsExpiredCerts + * + * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE) + * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). + * Corresponding deprecated method: SSLSetEnableCertVerify + * + * - kCFStreamSSLLevel (UNAVAILABLE) + * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead. + * Corresponding deprecated method: SSLSetProtocolVersionEnabled + * + * + * Please refer to Apple's documentation for corresponding SSLFunctions. + * * If you pass in nil or an empty dictionary, the default settings will be used. * + * IMPORTANT SECURITY NOTE: * The default settings will check to make sure the remote party's certificate is signed by a * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. * However it will not verify the name on the certificate unless you @@ -704,12 +801,10 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * the default settings will not detect any problems since the certificate is valid. * To properly secure your connection in this particular scenario you * should set the kCFStreamSSLPeerName property to "MySecureServer.com". - * If you do not know the peer name of the remote host in advance (for example, you're not sure - * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the - * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. - * The X509Certificate class is part of the CocoaAsyncSocket open source project. - **/ -- (void)startTLS:(NSDictionary *)tlsSettings; + * + * You can also perform additional validation in socketDidSecure. +**/ +- (void)startTLS:(nullable NSDictionary *)tlsSettings; #pragma mark Advanced @@ -743,8 +838,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * * The default value is YES. **/ -- (BOOL)autoDisconnectOnClosedReadStream; -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag; +@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream; /** * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. @@ -884,8 +978,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * * See also: (BOOL)enableBackgroundingOnSocket **/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; /** * This method is only available from within the context of a performBlock: invocation. @@ -916,26 +1010,42 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; #endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - /** * This method is only available from within the context of a performBlock: invocation. * See the documentation for the performBlock: method above. * * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket. **/ -- (SSLContextRef)sslContext; - -#endif +- (nullable SSLContextRef)sslContext; #pragma mark Utilities +/** + * The address lookup utility used by the class. + * This method is synchronous, so it's recommended you use it on a background thread/queue. + * + * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6. + * + * @returns + * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo. + * The addresses are specifically for TCP connections. + * You can filter the addresses, if needed, using the other utility methods provided by the class. +**/ ++ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr; + /** * Extracting host and port information from raw address data. **/ -+ (NSString *)hostFromAddress:(NSData *)address; + ++ (nullable NSString *)hostFromAddress:(NSData *)address; + (uint16_t)portFromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; + ++ (BOOL)isIPv4Address:(NSData *)address; ++ (BOOL)isIPv6Address:(NSData *)address; + ++ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address; + ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address; /** * A few common line separators, for use with the readDataToData:... methods. @@ -951,7 +1061,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -@protocol GCDAsyncSocketDelegate +@protocol GCDAsyncSocketDelegate @optional /** @@ -972,7 +1082,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * dispatch_retain(myExistingQueue); * return myExistingQueue; **/ -- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; +- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; /** * Called when a socket accepts a connection. @@ -992,6 +1102,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; +/** + * Called when a socket connects and is ready for reading and writing. + * The host parameter will be an IP address, not a DNS name. + **/ +- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url; + /** * Called when a socket has completed reading the requested data into memory. * Not called if there is an error. @@ -1001,7 +1117,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; /** * Called when a socket has read in data, but has not yet completed the read. * This would occur if using readToData: or readToLength: methods. - * It may be used to for things such as updating progress bars. + * It may be used for things such as updating progress bars. **/ - (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; @@ -1012,7 +1128,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; /** * Called when a socket has written some data, but has not yet completed the entire write. - * It may be used to for things such as updating progress bars. + * It may be used for things such as updating progress bars. **/ - (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; @@ -1058,9 +1174,24 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; * Called when a socket disconnects with or without error. * * If you call the disconnect method, and the socket wasn't already disconnected, - * this delegate method will be called before the disconnect method returns. + * then an invocation of this delegate method will be enqueued on the delegateQueue + * before the disconnect method returns. + * + * Note: If the GCDAsyncSocket instance is deallocated while it is still connected, + * and the delegate is not also deallocated, then this method will be invoked, + * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.) + * This is a generally rare, but is possible if one writes code like this: + * + * asyncSocket = nil; // I'm implicitly disconnecting the socket + * + * In this case it may preferrable to nil the delegate beforehand, like this: + * + * asyncSocket.delegate = nil; // Don't invoke my delegate method + * asyncSocket = nil; // I'm implicitly disconnecting the socket + * + * Of course, this depends on how your state machine is configured. **/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err; +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err; /** * Called after the socket has successfully completed SSL/TLS negotiation. @@ -1071,4 +1202,25 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; **/ - (void)socketDidSecure:(GCDAsyncSocket *)sock; +/** + * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to. + * + * This is only called if startTLS is invoked with options that include: + * - GCDAsyncSocketManuallyEvaluateTrust == YES + * + * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer. + * + * Note from Apple's documentation: + * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain, + * [it] might block while attempting network access. You should never call it from your main thread; + * call it only from within a function running on a dispatch queue or on a separate thread. + * + * Thus this method uses a completionHandler block rather than a normal return value. + * The completionHandler block is thread-safe, and may be invoked from a background queue/thread. + * It is safe to invoke the completionHandler block even if the socket has been closed. +**/ +- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust + completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler; + @end +NS_ASSUME_NONNULL_END diff --git a/Ifish/AsyncSocket/GCD/GCDAsyncSocket.m b/Ifish/AsyncSocket/GCD/GCDAsyncSocket.m index 5722ac3..f3d1c17 100755 --- a/Ifish/AsyncSocket/GCD/GCDAsyncSocket.m +++ b/Ifish/AsyncSocket/GCD/GCDAsyncSocket.m @@ -14,6 +14,7 @@ #import #endif +#import #import #import #import @@ -25,6 +26,7 @@ #import #import #import +#import #import #if ! __has_feature(objc_arc) @@ -32,34 +34,12 @@ // For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC #endif -/** - * Does ARC support support GCD objects? - * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+ -**/ -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif +#ifndef GCDAsyncSocketLoggingEnabled +#define GCDAsyncSocketLoggingEnabled 0 #endif - -#if 0 +#if GCDAsyncSocketLoggingEnabled // Logging Enabled - See log level below @@ -70,7 +50,7 @@ #import "DDLog.h" #define LogAsync YES -#define LogContext 65535 +#define LogContext GCDAsyncSocketLoggingContext #define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) #define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) @@ -88,8 +68,12 @@ #define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) #define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) +#ifndef GCDAsyncSocketLogLevel +#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE +#endif + // Log levels : off, error, warn, info, verbose -static const int logLevel = LOG_LEVEL_VERBOSE; +static const int logLevel = GCDAsyncSocketLogLevel; #else @@ -131,15 +115,20 @@ NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; +NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust"; #if TARGET_OS_IPHONE +NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS"; +#endif +NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID"; NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -#else +NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart"; +NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord"; +NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; +NSString *const GCDAsyncSocketSSLALPN = @"GCDAsyncSocketSSLALPN"; +#if !TARGET_OS_IPHONE NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; #endif -#endif enum GCDAsyncSocketFlags { @@ -159,10 +148,11 @@ enum GCDAsyncSocketFlags kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained + kDealloc = 1 << 16, // If set, the socket is being deallocated #if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 16, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 17, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 18, // If set, CFReadStream has notified us of bytes available + kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread + kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport + kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available #endif }; @@ -176,166 +166,12 @@ enum GCDAsyncSocketConfig #if TARGET_OS_IPHONE static NSThread *cfstreamThread; // Used for CFStreams + + + static uint64_t cfstreamThreadRetainCount; // setup & teardown + static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown #endif -@interface GCDAsyncSocket () -{ - uint32_t flags; - uint16_t config; - -#if __has_feature(objc_arc_weak) - __weak id delegate; -#else - __unsafe_unretained id delegate; -#endif - dispatch_queue_t delegateQueue; - - int socket4FD; - int socket6FD; - int connectIndex; - NSData * connectInterface4; - NSData * connectInterface6; - - dispatch_queue_t socketQueue; - - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; - - NSMutableArray *readQueue; - NSMutableArray *writeQueue; - - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; - - unsigned long socketFDBytesAvailable; - - GCDAsyncSocketPreBuffer *preBuffer; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; -#endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; -#endif - - void *IsOnSocketQueueOrTargetQueueKey; - - id userData; -} -// Accepting -- (BOOL)doAccept:(int)socketFD; - -// Connecting -- (void)startConnectTimeout:(NSTimeInterval)timeout; -- (void)endConnectTimeout; -- (void)doConnectTimeout; -- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port; -- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6; -- (void)lookup:(int)aConnectIndex didFail:(NSError *)error; -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr; -- (void)didConnect:(int)aConnectIndex; -- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error; - -// Disconnect -- (void)closeWithError:(NSError *)error; -- (void)maybeClose; - -// Errors -- (NSError *)badConfigError:(NSString *)msg; -- (NSError *)badParamError:(NSString *)msg; -- (NSError *)gaiError:(int)gai_error; -- (NSError *)errnoError; -- (NSError *)errnoErrorWithReason:(NSString *)reason; -- (NSError *)connectTimeoutError; -- (NSError *)otherError:(NSString *)msg; - -// Diagnostics -- (NSString *)connectedHost4; -- (NSString *)connectedHost6; -- (uint16_t)connectedPort4; -- (uint16_t)connectedPort6; -- (NSString *)localHost4; -- (NSString *)localHost6; -- (uint16_t)localPort4; -- (uint16_t)localPort6; -- (NSString *)connectedHostFromSocket4:(int)socketFD; -- (NSString *)connectedHostFromSocket6:(int)socketFD; -- (uint16_t)connectedPortFromSocket4:(int)socketFD; -- (uint16_t)connectedPortFromSocket6:(int)socketFD; -- (NSString *)localHostFromSocket4:(int)socketFD; -- (NSString *)localHostFromSocket6:(int)socketFD; -- (uint16_t)localPortFromSocket4:(int)socketFD; -- (uint16_t)localPortFromSocket6:(int)socketFD; - -// Utilities -- (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr - address6:(NSMutableData **)addr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port; -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD; -- (void)suspendReadSource; -- (void)resumeReadSource; -- (void)suspendWriteSource; -- (void)resumeWriteSource; - -// Reading -- (void)maybeDequeueRead; -- (void)flushSSLBuffers; -- (void)doReadData; -- (void)doReadEOF; -- (void)completeCurrentRead; -- (void)endCurrentRead; -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout; -- (void)doReadTimeout; -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension; - -// Writing -- (void)maybeDequeueWrite; -- (void)doWriteData; -- (void)completeCurrentWrite; -- (void)endCurrentWrite; -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout; -- (void)doWriteTimeout; -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension; - -// Security -- (void)maybeStartTLS; -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -- (void)ssl_startTLS; -- (void)ssl_continueSSLHandshake; -#endif -#if TARGET_OS_IPHONE -- (void)cf_startTLS; -#endif - -// CFStream -#if TARGET_OS_IPHONE -+ (void)startCFStreamThreadIfNeeded; -- (BOOL)createReadAndWriteStream; -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite; -- (BOOL)addStreamsToRunLoop; -- (BOOL)openStreams; -- (void)removeStreamsFromRunLoop; -#endif - -// Class Methods -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; - -@end - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -367,7 +203,7 @@ enum GCDAsyncSocketConfig uint8_t *writePointer; } -- (id)initWithCapacity:(size_t)numBytes; +- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER; - (void)ensureCapacityForWrite:(size_t)numBytes; @@ -390,7 +226,14 @@ enum GCDAsyncSocketConfig @implementation GCDAsyncSocketPreBuffer -- (id)initWithCapacity:(size_t)numBytes +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithCapacity:(size_t)numBytes { if ((self = [super init])) { @@ -411,7 +254,7 @@ enum GCDAsyncSocketConfig - (void)ensureCapacityForWrite:(size_t)numBytes { - size_t availableSpace = preBufferSize - (writePointer - readPointer); + size_t availableSpace = [self availableSpace]; if (numBytes > availableSpace) { @@ -444,7 +287,7 @@ enum GCDAsyncSocketConfig - (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr { if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = writePointer - readPointer; + if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; } - (void)didRead:(size_t)bytesRead @@ -461,7 +304,7 @@ enum GCDAsyncSocketConfig - (size_t)availableSpace { - return preBufferSize - (writePointer - readPointer); + return preBufferSize - (writePointer - preBuffer); } - (uint8_t *)writeBuffer @@ -472,7 +315,7 @@ enum GCDAsyncSocketConfig - (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr { if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer); + if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; } - (void)didWrite:(size_t)bytesWritten @@ -513,13 +356,13 @@ enum GCDAsyncSocketConfig NSUInteger originalBufferLength; long tag; } -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i; +- (instancetype)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i NS_DESIGNATED_INITIALIZER; - (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; @@ -535,13 +378,20 @@ enum GCDAsyncSocketConfig @implementation GCDAsyncReadPacket -- (id)initWithData:(NSMutableData *)d - startOffset:(NSUInteger)s - maxLength:(NSUInteger)m - timeout:(NSTimeInterval)t - readLength:(NSUInteger)l - terminator:(NSData *)e - tag:(long)i +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithData:(NSMutableData *)d + startOffset:(NSUInteger)s + maxLength:(NSUInteger)m + timeout:(NSTimeInterval)t + readLength:(NSUInteger)l + terminator:(NSData *)e + tag:(long)i { if((self = [super init])) { @@ -606,8 +456,7 @@ enum GCDAsyncSocketConfig if (readLength > 0) { // Read a specific length of data - - result = MIN(defaultValue, (readLength - bytesDone)); + result = readLength - bytesDone; // There is no need to prebuffer since we know exactly how much data we need to read. // Even if the buffer isn't currently big enough to fit this amount of data, @@ -973,12 +822,19 @@ enum GCDAsyncSocketConfig long tag; NSTimeInterval timeout; } -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; @end @implementation GCDAsyncWritePacket -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i { if((self = [super init])) { @@ -1006,12 +862,19 @@ enum GCDAsyncSocketConfig @public NSDictionary *tlsSettings; } -- (id)initWithTLSSettings:(NSDictionary *)settings; +- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER; @end @implementation GCDAsyncSpecialPacket -- (id)initWithTLSSettings:(NSDictionary *)settings +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithTLSSettings:(NSDictionary *)settings { if((self = [super init])) { @@ -1028,36 +891,91 @@ enum GCDAsyncSocketConfig //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation GCDAsyncSocket +{ + uint32_t flags; + uint16_t config; + + __weak id delegate; + dispatch_queue_t delegateQueue; + + int socket4FD; + int socket6FD; + int socketUN; + NSURL *socketUrl; + int stateIndex; + NSData * connectInterface4; + NSData * connectInterface6; + NSData * connectInterfaceUN; + + dispatch_queue_t socketQueue; + + dispatch_source_t accept4Source; + dispatch_source_t accept6Source; + dispatch_source_t acceptUNSource; + dispatch_source_t connectTimer; + dispatch_source_t readSource; + dispatch_source_t writeSource; + dispatch_source_t readTimer; + dispatch_source_t writeTimer; + + NSMutableArray *readQueue; + NSMutableArray *writeQueue; + + GCDAsyncReadPacket *currentRead; + GCDAsyncWritePacket *currentWrite; + + unsigned long socketFDBytesAvailable; + + GCDAsyncSocketPreBuffer *preBuffer; + +#if TARGET_OS_IPHONE + CFStreamClientContext streamContext; + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; +#endif + SSLContextRef sslContext; + GCDAsyncSocketPreBuffer *sslPreBuffer; + size_t sslWriteCachedLength; + OSStatus sslErrCode; + OSStatus lastSSLHandshakeError; + + void *IsOnSocketQueueOrTargetQueueKey; + + id userData; + NSTimeInterval alternateAddressDelay; +} -- (id)init +- (instancetype)init { return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; } -- (id)initWithSocketQueue:(dispatch_queue_t)sq +- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq { return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq { return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq { if((self = [super init])) { delegate = aDelegate; delegateQueue = dq; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (dq) dispatch_retain(dq); #endif socket4FD = SOCKET_NULL; socket6FD = SOCKET_NULL; - connectIndex = 0; + socketUN = SOCKET_NULL; + socketUrl = nil; + stateIndex = 0; if (sq) { @@ -1069,7 +987,7 @@ enum GCDAsyncSocketConfig @"The given socketQueue parameter must not be a concurrent queue."); socketQueue = sq; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(sq); #endif } @@ -1107,6 +1025,7 @@ enum GCDAsyncSocketConfig currentWrite = nil; preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; + alternateAddressDelay = 0.3; } return self; } @@ -1115,6 +1034,10 @@ enum GCDAsyncSocketConfig { LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + // Set dealloc flag. + // This is used by closeWithError to ensure we don't accidentally retain ourself. + flags |= kDealloc; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { [self closeWithError:nil]; @@ -1128,12 +1051,12 @@ enum GCDAsyncSocketConfig delegate = nil; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (delegateQueue) dispatch_release(delegateQueue); #endif delegateQueue = NULL; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (socketQueue) dispatch_release(socketQueue); #endif socketQueue = NULL; @@ -1141,6 +1064,69 @@ enum GCDAsyncSocketConfig LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); } +#pragma mark - + ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error { + return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error]; +} + ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error { + return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error]; +} + ++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error +{ + __block BOOL errorOccured = NO; + + GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq]; + + dispatch_sync(socket->socketQueue, ^{ @autoreleasepool { + struct sockaddr addr; + socklen_t addr_size = sizeof(struct sockaddr); + int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size); + if (retVal) + { + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Attempt to create socket from socket FD failed. getpeername() failed", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + errorOccured = YES; + if (error) + *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; + return; + } + + if (addr.sa_family == AF_INET) + { + socket->socket4FD = socketFD; + } + else if (addr.sa_family == AF_INET6) + { + socket->socket6FD = socketFD; + } + else + { + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", + @"GCDAsyncSocket", [NSBundle mainBundle], + @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", nil); + + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + errorOccured = YES; + if (error) + *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; + return; + } + + socket->flags = kSocketStarted; + [socket didConnect:socket->stateIndex]; + }}); + + return errorOccured? nil: socket; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Configuration //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1156,7 +1142,7 @@ enum GCDAsyncSocketConfig __block id result; dispatch_sync(socketQueue, ^{ - result = delegate; + result = self->delegate; }); return result; @@ -1166,7 +1152,7 @@ enum GCDAsyncSocketConfig - (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ - delegate = newDelegate; + self->delegate = newDelegate; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -1180,12 +1166,12 @@ enum GCDAsyncSocketConfig } } -- (void)setDelegate:(id)newDelegate +- (void)setDelegate:(id)newDelegate { [self setDelegate:newDelegate synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate +- (void)synchronouslySetDelegate:(id)newDelegate { [self setDelegate:newDelegate synchronously:YES]; } @@ -1201,7 +1187,7 @@ enum GCDAsyncSocketConfig __block dispatch_queue_t result; dispatch_sync(socketQueue, ^{ - result = delegateQueue; + result = self->delegateQueue; }); return result; @@ -1212,12 +1198,12 @@ enum GCDAsyncSocketConfig { dispatch_block_t block = ^{ - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); if (newDelegateQueue) dispatch_retain(newDelegateQueue); #endif - delegateQueue = newDelegateQueue; + self->delegateQueue = newDelegateQueue; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -1241,7 +1227,7 @@ enum GCDAsyncSocketConfig [self setDelegateQueue:newDelegateQueue synchronously:YES]; } -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr { if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -1254,8 +1240,8 @@ enum GCDAsyncSocketConfig __block dispatch_queue_t dqPtr = NULL; dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; + dPtr = self->delegate; + dqPtr = self->delegateQueue; }); if (delegatePtr) *delegatePtr = dPtr; @@ -1267,14 +1253,14 @@ enum GCDAsyncSocketConfig { dispatch_block_t block = ^{ - delegate = newDelegate; + self->delegate = newDelegate; - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); if (newDelegateQueue) dispatch_retain(newDelegateQueue); #endif - delegateQueue = newDelegateQueue; + self->delegateQueue = newDelegateQueue; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -1288,12 +1274,12 @@ enum GCDAsyncSocketConfig } } -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; } @@ -1311,7 +1297,7 @@ enum GCDAsyncSocketConfig __block BOOL result; dispatch_sync(socketQueue, ^{ - result = ((config & kIPv4Disabled) == 0); + result = ((self->config & kIPv4Disabled) == 0); }); return result; @@ -1325,9 +1311,9 @@ enum GCDAsyncSocketConfig dispatch_block_t block = ^{ if (flag) - config &= ~kIPv4Disabled; + self->config &= ~kIPv4Disabled; else - config |= kIPv4Disabled; + self->config |= kIPv4Disabled; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -1349,7 +1335,7 @@ enum GCDAsyncSocketConfig __block BOOL result; dispatch_sync(socketQueue, ^{ - result = ((config & kIPv6Disabled) == 0); + result = ((self->config & kIPv6Disabled) == 0); }); return result; @@ -1363,9 +1349,9 @@ enum GCDAsyncSocketConfig dispatch_block_t block = ^{ if (flag) - config &= ~kIPv6Disabled; + self->config &= ~kIPv6Disabled; else - config |= kIPv6Disabled; + self->config |= kIPv6Disabled; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -1387,23 +1373,23 @@ enum GCDAsyncSocketConfig __block BOOL result; dispatch_sync(socketQueue, ^{ - result = ((config & kPreferIPv6) == 0); + result = ((self->config & kPreferIPv6) == 0); }); return result; } } -- (void)setPreferIPv4OverIPv6:(BOOL)flag +- (void)setIPv4PreferredOverIPv6:(BOOL)flag { // Note: YES means kPreferIPv6 is OFF dispatch_block_t block = ^{ if (flag) - config &= ~kPreferIPv6; + self->config &= ~kPreferIPv6; else - config |= kPreferIPv6; + self->config |= kPreferIPv6; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -1412,13 +1398,35 @@ enum GCDAsyncSocketConfig dispatch_async(socketQueue, block); } +- (NSTimeInterval) alternateAddressDelay { + __block NSTimeInterval delay; + dispatch_block_t block = ^{ + delay = self->alternateAddressDelay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + return delay; +} + +- (void) setAlternateAddressDelay:(NSTimeInterval)delay { + dispatch_block_t block = ^{ + self->alternateAddressDelay = delay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + - (id)userData { __block id result = nil; dispatch_block_t block = ^{ - result = userData; + result = self->userData; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -1433,9 +1441,9 @@ enum GCDAsyncSocketConfig { dispatch_block_t block = ^{ - if (userData != arbitraryUserData) + if (self->userData != arbitraryUserData) { - userData = arbitraryUserData; + self->userData = arbitraryUserData; } }; @@ -1474,7 +1482,7 @@ enum GCDAsyncSocketConfig if (socketFD == SOCKET_NULL) { NSString *reason = @"Error in socket() function"; - err = [self errnoErrorWithReason:reason]; + err = [self errorWithErrno:errno reason:reason]; return SOCKET_NULL; } @@ -1487,7 +1495,7 @@ enum GCDAsyncSocketConfig if (status == -1) { NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errnoErrorWithReason:reason]; + err = [self errorWithErrno:errno reason:reason]; LogVerbose(@"close(socketFD)"); close(socketFD); @@ -1499,7 +1507,7 @@ enum GCDAsyncSocketConfig if (status == -1) { NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errnoErrorWithReason:reason]; + err = [self errorWithErrno:errno reason:reason]; LogVerbose(@"close(socketFD)"); close(socketFD); @@ -1512,7 +1520,7 @@ enum GCDAsyncSocketConfig if (status == -1) { NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; + err = [self errorWithErrno:errno reason:reason]; LogVerbose(@"close(socketFD)"); close(socketFD); @@ -1525,7 +1533,7 @@ enum GCDAsyncSocketConfig if (status == -1) { NSString *reason = @"Error in listen() function"; - err = [self errnoErrorWithReason:reason]; + err = [self errorWithErrno:errno reason:reason]; LogVerbose(@"close(socketFD)"); close(socketFD); @@ -1539,7 +1547,7 @@ enum GCDAsyncSocketConfig dispatch_block_t block = ^{ @autoreleasepool { - if (delegate == nil) // Must have delegate set + if (self->delegate == nil) // Must have delegate set { NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; err = [self badConfigError:msg]; @@ -1547,7 +1555,7 @@ enum GCDAsyncSocketConfig return_from_block; } - if (delegateQueue == NULL) // Must have delegate queue set + if (self->delegateQueue == NULL) // Must have delegate queue set { NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; err = [self badConfigError:msg]; @@ -1555,8 +1563,8 @@ enum GCDAsyncSocketConfig return_from_block; } - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled { @@ -1575,8 +1583,8 @@ enum GCDAsyncSocketConfig } // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; + [self->readQueue removeAllObjects]; + [self->writeQueue removeAllObjects]; // Resolve interface from description @@ -1617,9 +1625,9 @@ enum GCDAsyncSocketConfig if (enableIPv4) { LogVerbose(@"Creating IPv4 socket"); - socket4FD = createSocket(AF_INET, interface4); + self->socket4FD = createSocket(AF_INET, interface4); - if (socket4FD == SOCKET_NULL) + if (self->socket4FD == SOCKET_NULL) { return_from_block; } @@ -1638,14 +1646,15 @@ enum GCDAsyncSocketConfig addr6->sin6_port = htons([self localPort4]); } - socket6FD = createSocket(AF_INET6, interface6); + self->socket6FD = createSocket(AF_INET6, interface6); - if (socket6FD == SOCKET_NULL) + if (self->socket6FD == SOCKET_NULL) { - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { LogVerbose(@"close(socket4FD)"); - close(socket4FD); + close(self->socket4FD); + self->socket4FD = SOCKET_NULL; } return_from_block; @@ -1656,12 +1665,19 @@ enum GCDAsyncSocketConfig if (enableIPv4) { - accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); + self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket4FD, 0, self->socketQueue); - int socketFD = socket4FD; - dispatch_source_t acceptSource = accept4Source; + int socketFD = self->socket4FD; + dispatch_source_t acceptSource = self->accept4Source; - dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(self->accept4Source, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; LogVerbose(@"event4Block"); @@ -1670,32 +1686,46 @@ enum GCDAsyncSocketConfig LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - while ([self doAccept:socketFD] && (++i < numPendingConnections)); + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + + #pragma clang diagnostic pop }}); - dispatch_source_set_cancel_handler(accept4Source, ^{ + + dispatch_source_set_cancel_handler(self->accept4Source, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(accept4Source)"); dispatch_release(acceptSource); #endif LogVerbose(@"close(socket4FD)"); close(socketFD); + + #pragma clang diagnostic pop }); LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(accept4Source); + dispatch_resume(self->accept4Source); } if (enableIPv6) { - accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); + self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket6FD, 0, self->socketQueue); - int socketFD = socket6FD; - dispatch_source_t acceptSource = accept6Source; + int socketFD = self->socket6FD; + dispatch_source_t acceptSource = self->accept6Source; - dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(self->accept6Source, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; LogVerbose(@"event6Block"); @@ -1704,25 +1734,31 @@ enum GCDAsyncSocketConfig LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - while ([self doAccept:socketFD] && (++i < numPendingConnections)); + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + + #pragma clang diagnostic pop }}); - dispatch_source_set_cancel_handler(accept6Source, ^{ + dispatch_source_set_cancel_handler(self->accept6Source, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(accept6Source)"); dispatch_release(acceptSource); #endif LogVerbose(@"close(socket6FD)"); close(socketFD); + + #pragma clang diagnostic pop }); LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(accept6Source); + dispatch_resume(self->accept6Source); } - flags |= kSocketStarted; + self->flags |= kSocketStarted; result = YES; }}; @@ -1743,17 +1779,223 @@ enum GCDAsyncSocketConfig return result; } +- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr +{ + LogTrace(); + + __block BOOL result = NO; + __block NSError *err = nil; + + // CreateSocket Block + // This block will be invoked within the dispatch block below. + + int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { + + int socketFD = socket(domain, SOCK_STREAM, 0); + + if (socketFD == SOCKET_NULL) + { + NSString *reason = @"Error in socket() function"; + err = [self errorWithErrno:errno reason:reason]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; + err = [self errorWithErrno:errno reason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + int reuseOn = 1; + status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + if (status == -1) + { + NSString *reason = @"Error enabling address reuse (setsockopt)"; + err = [self errorWithErrno:errno reason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Bind socket + + status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); + if (status == -1) + { + NSString *reason = @"Error in bind() function"; + err = [self errorWithErrno:errno reason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + // Listen + + status = listen(socketFD, 1024); + if (status == -1) + { + NSString *reason = @"Error in listen() function"; + err = [self errorWithErrno:errno reason:reason]; + + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } + + return socketFD; + }; + + // Create dispatch block and run on socketQueue + + dispatch_block_t block = ^{ @autoreleasepool { + + if (self->delegate == nil) // Must have delegate set + { + NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (self->delegateQueue == NULL) // Must have delegate queue set + { + NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + if (![self isDisconnected]) // Must be disconnected + { + NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; + err = [self badConfigError:msg]; + + return_from_block; + } + + // Clear queues (spurious read/write requests post disconnect) + [self->readQueue removeAllObjects]; + [self->writeQueue removeAllObjects]; + + // Remove a previous socket + + NSError *error = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *urlPath = url.path; + if (urlPath && [fileManager fileExistsAtPath:urlPath]) { + if (![fileManager removeItemAtURL:url error:&error]) { + NSString *msg = @"Could not remove previous unix domain socket at given url."; + err = [self otherError:msg]; + + return_from_block; + } + } + + // Resolve interface from description + + NSData *interface = [self getInterfaceAddressFromUrl:url]; + + if (interface == nil) + { + NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; + err = [self badParamError:msg]; + + return_from_block; + } + + // Create sockets, configure, bind, and listen + + LogVerbose(@"Creating unix domain socket"); + self->socketUN = createSocket(AF_UNIX, interface); + + if (self->socketUN == SOCKET_NULL) + { + return_from_block; + } + + self->socketUrl = url; + + // Create accept sources + + self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socketUN, 0, self->socketQueue); + + int socketFD = self->socketUN; + dispatch_source_t acceptSource = self->acceptUNSource; + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(self->acceptUNSource, ^{ @autoreleasepool { + + __strong GCDAsyncSocket *strongSelf = weakSelf; + + LogVerbose(@"eventUNBlock"); + + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + }}); + + dispatch_source_set_cancel_handler(self->acceptUNSource, ^{ + +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(acceptUNSource)"); + dispatch_release(acceptSource); +#endif + + LogVerbose(@"close(socketUN)"); + close(socketFD); + }); + + LogVerbose(@"dispatch_resume(acceptUNSource)"); + dispatch_resume(self->acceptUNSource); + + self->flags |= kSocketStarted; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + LogInfo(@"Error in accept: %@", err); + + if (errPtr) + *errPtr = err; + } + + return result; +} + - (BOOL)doAccept:(int)parentSocketFD { LogTrace(); - BOOL isIPv4; + int socketType; int childSocketFD; NSData *childSocketAddress; if (parentSocketFD == socket4FD) { - isIPv4 = YES; + socketType = 0; struct sockaddr_in addr; socklen_t addrLen = sizeof(addr); @@ -1768,9 +2010,9 @@ enum GCDAsyncSocketConfig childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; } - else // if (parentSocketFD == socket6FD) + else if (parentSocketFD == socket6FD) { - isIPv4 = NO; + socketType = 1; struct sockaddr_in6 addr; socklen_t addrLen = sizeof(addr); @@ -1785,6 +2027,23 @@ enum GCDAsyncSocketConfig childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; } + else // if (parentSocketFD == socketUN) + { + socketType = 2; + + struct sockaddr_un addr; + socklen_t addrLen = sizeof(addr); + + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } + + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } // Enable non-blocking IO on the socket @@ -1792,6 +2051,8 @@ enum GCDAsyncSocketConfig if (result == -1) { LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); + LogVerbose(@"close(childSocketFD)"); + close(childSocketFD); return NO; } @@ -1804,7 +2065,7 @@ enum GCDAsyncSocketConfig if (delegateQueue) { - __strong id theDelegate = delegate; + __strong id theDelegate = delegate; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -1820,14 +2081,16 @@ enum GCDAsyncSocketConfig // Create GCDAsyncSocket instance for accepted socket - GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate - delegateQueue:delegateQueue - socketQueue:childSocketQueue]; + GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate + delegateQueue:self->delegateQueue + socketQueue:childSocketQueue]; - if (isIPv4) + if (socketType == 0) acceptedSocket->socket4FD = childSocketFD; - else + else if (socketType == 1) acceptedSocket->socket6FD = childSocketFD; + else + acceptedSocket->socketUN = childSocketFD; acceptedSocket->flags = (kSocketStarted | kConnected); @@ -1846,7 +2109,7 @@ enum GCDAsyncSocketConfig } // Release the socket queue returned from the delegate (it was retained by acceptedSocket) - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (childSocketQueue) dispatch_release(childSocketQueue); #endif @@ -1962,6 +2225,61 @@ enum GCDAsyncSocketConfig return YES; } +- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (![self isDisconnected]) // Must be disconnected + { + if (errPtr) + { + NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + NSData *interface = [self getInterfaceAddressFromUrl:url]; + + if (interface == nil) + { + if (errPtr) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + connectInterfaceUN = interface; + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + return YES; +} + - (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr { return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; @@ -1988,7 +2306,7 @@ enum GCDAsyncSocketConfig NSString *interface = [inInterface copy]; __block BOOL result = NO; - __block NSError *err = nil; + __block NSError *preConnectErr = nil; dispatch_block_t block = ^{ @autoreleasepool { @@ -1997,14 +2315,14 @@ enum GCDAsyncSocketConfig if ([host length] == 0) { NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - err = [self badParamError:msg]; + preConnectErr = [self badParamError:msg]; return_from_block; } // Run through standard pre-connect checks - if (![self preConnectWithInterface:interface error:&err]) + if (![self preConnectWithInterface:interface error:&preConnectErr]) { return_from_block; } @@ -2012,7 +2330,7 @@ enum GCDAsyncSocketConfig // We've made it past all the checks. // It's time to start the connection process. - flags |= kSocketStarted; + self->flags |= kSocketStarted; LogVerbose(@"Dispatching DNS lookup..."); @@ -2020,13 +2338,53 @@ enum GCDAsyncSocketConfig // So we want to copy it now, within this block that will be executed synchronously. // This way the asynchronous lookup block below doesn't have to worry about it changing. - int aConnectIndex = connectIndex; NSString *hostCpy = [host copy]; + int aStateIndex = self->stateIndex; + __weak GCDAsyncSocket *weakSelf = self; + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" - [self lookup:aConnectIndex host:hostCpy port:port]; + NSError *lookupErr = nil; + NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + if (lookupErr) + { + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + [strongSelf lookup:aStateIndex didFail:lookupErr]; + }}); + } + else + { + NSData *address4 = nil; + NSData *address6 = nil; + + for (NSData *address in addresses) + { + if (!address4 && [[self class] isIPv4Address:address]) + { + address4 = address; + } + else if (!address6 && [[self class] isIPv6Address:address]) + { + address6 = address; + } + } + + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; + }}); + } + + #pragma clang diagnostic pop }}); [self startConnectTimeout:timeout]; @@ -2039,12 +2397,8 @@ enum GCDAsyncSocketConfig else dispatch_sync(socketQueue, block); - if (result == NO) - { - if (errPtr) - *errPtr = err; - } + if (errPtr) *errPtr = preConnectErr; return result; } @@ -2107,8 +2461,8 @@ enum GCDAsyncSocketConfig return_from_block; } - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; if (isIPv4Disabled && (address4 != nil)) { @@ -2141,7 +2495,7 @@ enum GCDAsyncSocketConfig return_from_block; } - flags |= kSocketStarted; + self->flags |= kSocketStarted; [self startConnectTimeout:timeout]; @@ -2162,109 +2516,89 @@ enum GCDAsyncSocketConfig return result; } -- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port +- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr { LogTrace(); - // This method is executed on a global concurrent queue. - // It posts the results back to the socket queue. - // The lookupIndex is used to ignore the results if the connect operation was cancelled or timed out. + __block BOOL result = NO; + __block NSError *err = nil; - NSError *error = nil; - - NSData *address4 = nil; - NSData *address6 = nil; - - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr; - nativeAddr.sin_len = sizeof(struct sockaddr_in); - nativeAddr.sin_family = AF_INET; - nativeAddr.sin_port = htons(port); - nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); + dispatch_block_t block = ^{ @autoreleasepool { - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; + // Check for problems with host parameter - // Wrap the native address structures - address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; - address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) + if ([url.path length] == 0) { - error = [self gaiError:gai_error]; - } - else - { - for(res = res0; res; res = res->ai_next) - { - if ((address4 == nil) && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structure - address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if ((address6 == nil) && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structure - address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); + NSString *msg = @"Invalid unix domain socket url."; + err = [self badParamError:msg]; - if ((address4 == nil) && (address6 == nil)) - { - error = [self gaiError:EAI_FAIL]; - } + return_from_block; } + + // Run through standard pre-connect checks + + if (![self preConnectWithUrl:url error:&err]) + { + return_from_block; + } + + // We've made it past all the checks. + // It's time to start the connection process. + + self->flags |= kSocketStarted; + + // Start the normal connection process + + NSError *connectError = nil; + if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError]) + { + [self closeWithError:connectError]; + + return_from_block; + } + + [self startConnectTimeout:timeout]; + + result = YES; + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (result == NO) + { + if (errPtr) + *errPtr = err; } - if (error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didFail:error]; - }}); - } - else - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } + return result; } -- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 +- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr +{ + NSArray* addresses = [netService addresses]; + for (NSData* address in addresses) + { + BOOL result = [self connectToAddress:address error:errPtr]; + if (result) + { + return YES; + } + } + + return NO; +} + +- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 { LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); NSAssert(address4 || address6, @"Expected at least one valid address"); - if (aConnectIndex != connectIndex) + if (aStateIndex != stateIndex) { LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); @@ -2311,14 +2645,14 @@ enum GCDAsyncSocketConfig * the original connection request may have already been cancelled or timed-out by the time this method is invoked. * The lookupIndex tells us whether the lookup is still valid or not. **/ -- (void)lookup:(int)aConnectIndex didFail:(NSError *)error +- (void)lookup:(int)aStateIndex didFail:(NSError *)error { LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - if (aConnectIndex != connectIndex) + if (aStateIndex != stateIndex) { LogInfo(@"Ignoring lookup:didFail: - already disconnected"); @@ -2331,6 +2665,154 @@ enum GCDAsyncSocketConfig [self closeWithError:error]; } +- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr +{ + // Bind the socket to the desired interface (if needed) + + if (connectInterface) + { + LogVerbose(@"Binding socket..."); + + if ([[self class] portFromAddress:connectInterface] > 0) + { + // Since we're going to be binding to a specific port, + // we should turn on reuseaddr to allow us to override sockets in time_wait. + + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + } + + const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; + + int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); + if (result != 0) + { + if (errPtr) + *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"]; + + return NO; + } + } + + return YES; +} + +- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr +{ + int socketFD = socket(family, SOCK_STREAM, 0); + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; + + return socketFD; + } + + if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr]) + { + [self closeSocket:socketFD]; + + return SOCKET_NULL; + } + + // Prevent SIGPIPE signals + + int nosigpipe = 1; + setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + + return socketFD; +} + +- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex +{ + // If there already is a socket connected, we close socketFD and return + if (self.isConnected) + { + [self closeSocket:socketFD]; + return; + } + + // Start the connection process in a background queue + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(globalConcurrentQueue, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" + + int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); + int err = errno; + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + if (strongSelf.isConnected) + { + [strongSelf closeSocket:socketFD]; + return_from_block; + } + + if (result == 0) + { + [self closeUnusedSocket:socketFD]; + + [strongSelf didConnect:aStateIndex]; + } + else + { + [strongSelf closeSocket:socketFD]; + + // If there are no more sockets trying to connect, we inform the error to the delegate + if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL) + { + NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"]; + [strongSelf didNotConnect:aStateIndex error:error]; + } + } + }}); + +#pragma clang diagnostic pop + }); + + LogVerbose(@"Connecting..."); +} + +- (void)closeSocket:(int)socketFD +{ + if (socketFD != SOCKET_NULL && + (socketFD == socket6FD || socketFD == socket4FD)) + { + close(socketFD); + + if (socketFD == socket4FD) + { + LogVerbose(@"close(socket4FD)"); + socket4FD = SOCKET_NULL; + } + else if (socketFD == socket6FD) + { + LogVerbose(@"close(socket6FD)"); + socket6FD = SOCKET_NULL; + } + } +} + +- (void)closeUnusedSocket:(int)usedSocketFD +{ + if (usedSocketFD != socket4FD) + { + [self closeSocket:socket4FD]; + } + else if (usedSocketFD != socket6FD) + { + [self closeSocket:socket6FD]; + } +} + - (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr { LogTrace(); @@ -2344,69 +2826,100 @@ enum GCDAsyncSocketConfig BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); + // Create and bind the sockets + + if (address4) + { + LogVerbose(@"Creating IPv4 socket"); + + socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr]; + } + + if (address6) + { + LogVerbose(@"Creating IPv6 socket"); + + socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr]; + } + + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + return NO; + } + + int socketFD, alternateSocketFD; + NSData *address, *alternateAddress; + + if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL) + { + socketFD = socket6FD; + alternateSocketFD = socket4FD; + address = address6; + alternateAddress = address4; + } + else + { + socketFD = socket4FD; + alternateSocketFD = socket6FD; + address = address4; + alternateAddress = address6; + } + + int aStateIndex = stateIndex; + + [self connectSocket:socketFD address:address stateIndex:aStateIndex]; + + if (alternateAddress) + { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{ + [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex]; + }); + } + + return YES; +} + +- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr +{ + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); // Create the socket int socketFD; - NSData *address; - NSData *connectInterface; - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = socket(AF_INET6, SOCK_STREAM, 0); - - socketFD = socket6FD; - address = address6; - connectInterface = connectInterface6; - } - else - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = socket(AF_INET, SOCK_STREAM, 0); - - socketFD = socket4FD; - address = address4; - connectInterface = connectInterface4; - } + LogVerbose(@"Creating unix domain socket"); + + socketUN = socket(AF_UNIX, SOCK_STREAM, 0); + + socketFD = socketUN; if (socketFD == SOCKET_NULL) { if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; return NO; } // Bind the socket to the desired interface (if needed) - if (connectInterface) - { - LogVerbose(@"Binding socket..."); - - if ([[self class] portFromAddress:connectInterface] > 0) - { - // Since we're going to be binding to a specific port, - // we should turn on reuseaddr to allow us to override sockets in time_wait. - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - } - - const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; - - int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); - if (result != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; - - return NO; - } - } + LogVerbose(@"Binding socket..."); + + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + +// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; +// +// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); +// if (result != 0) +// { +// if (errPtr) +// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; +// +// return NO; +// } // Prevent SIGPIPE signals @@ -2415,26 +2928,29 @@ enum GCDAsyncSocketConfig // Start the connection process in a background queue - int aConnectIndex = connectIndex; + int aStateIndex = stateIndex; dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalConcurrentQueue, ^{ - int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); + const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; + int result = connect(socketFD, addr, addr->sa_len); if (result == 0) { - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self didConnect:aConnectIndex]; + [self didConnect:aStateIndex]; }}); } else { - NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; + // TODO: Bad file descriptor + perror("connect"); + NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self didNotConnect:aConnectIndex error:error]; + [self didNotConnect:aStateIndex error:error]; }}); } }); @@ -2444,14 +2960,14 @@ enum GCDAsyncSocketConfig return YES; } -- (void)didConnect:(int)aConnectIndex +- (void)didConnect:(int)aStateIndex { LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - if (aConnectIndex != connectIndex) + if (aStateIndex != stateIndex) { LogInfo(@"Ignoring didConnect, already disconnected"); @@ -2465,8 +2981,8 @@ enum GCDAsyncSocketConfig [self endConnectTimeout]; #if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the connectIndex. - aConnectIndex = connectIndex; + // The endConnectTimeout method executed above incremented the stateIndex. + aStateIndex = stateIndex; #endif // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) @@ -2498,7 +3014,7 @@ enum GCDAsyncSocketConfig dispatch_block_t SetupStreamsPart2 = ^{ #if TARGET_OS_IPHONE - if (aConnectIndex != connectIndex) + if (aStateIndex != self->stateIndex) { // The socket has been disconnected. return; @@ -2523,18 +3039,33 @@ enum GCDAsyncSocketConfig NSString *host = [self connectedHost]; uint16_t port = [self connectedPort]; + NSURL *url = [self connectedUrl]; - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) { SetupStreamsPart1(); - __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate socket:self didConnectToHost:host port:port]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + SetupStreamsPart2(); + }}); + }}); + } + else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) + { + SetupStreamsPart1(); + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didConnectToUrl:url]; + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { SetupStreamsPart2(); }}); @@ -2548,7 +3079,7 @@ enum GCDAsyncSocketConfig // Get the connected socket - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; // Enable non-blocking IO on the socket @@ -2571,14 +3102,14 @@ enum GCDAsyncSocketConfig [self maybeDequeueWrite]; } -- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error +- (void)didNotConnect:(int)aStateIndex error:(NSError *)error { LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - if (aConnectIndex != connectIndex) + if (aStateIndex != stateIndex) { LogInfo(@"Ignoring didNotConnect, already disconnected"); @@ -2596,20 +3127,34 @@ enum GCDAsyncSocketConfig { connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + __weak GCDAsyncSocket *weakSelf = self; + dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - [self doConnectTimeout]; + [strongSelf doConnectTimeout]; + + #pragma clang diagnostic pop }}); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theConnectTimer = connectTimer; dispatch_source_set_cancel_handler(connectTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + LogVerbose(@"dispatch_release(connectTimer)"); dispatch_release(theConnectTimer); + + #pragma clang diagnostic pop }); #endif - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); dispatch_resume(connectTimer); @@ -2626,13 +3171,13 @@ enum GCDAsyncSocketConfig connectTimer = NULL; } - // Increment connectIndex. + // Increment stateIndex. // This will prevent us from processing results from any related background asynchronous operations. // // Note: This should be called from close method even if connectTimer is NULL. // This is because one might disconnect a socket prior to a successful connection which had no timeout. - connectIndex++; + stateIndex++; if (connectInterface4) { @@ -2659,10 +3204,8 @@ enum GCDAsyncSocketConfig - (void)closeWithError:(NSError *)error { LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - [self endConnectTimeout]; if (currentRead != nil) [self endCurrentRead]; @@ -2696,35 +3239,32 @@ enum GCDAsyncSocketConfig } } #endif - #if SECURE_TRANSPORT_MAYBE_AVAILABLE + + [sslPreBuffer reset]; + sslErrCode = lastSSLHandshakeError = noErr; + + if (sslContext) { - [sslPreBuffer reset]; - sslErrCode = noErr; + // Getting a linker error here about the SSLx() functions? + // You need to add the Security Framework to your application. - if (sslContext) - { - // Getting a linker error here about the SSLx() functions? - // You need to add the Security Framework to your application. - - SSLClose(sslContext); - - #if TARGET_OS_IPHONE - CFRelease(sslContext); - #else - SSLDisposeContext(sslContext); - #endif - - sslContext = NULL; - } + SSLClose(sslContext); + + #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) + CFRelease(sslContext); + #else + SSLDisposeContext(sslContext); + #endif + + sslContext = NULL; } - #endif // For some crazy reason (in my opinion), cancelling a dispatch source doesn't // invoke the cancel handler if the dispatch source is paused. // So we have to unpause the source if needed. // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - if (!accept4Source && !accept6Source && !readSource && !writeSource) + if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource) { LogVerbose(@"manually closing close"); @@ -2741,6 +3281,15 @@ enum GCDAsyncSocketConfig close(socket6FD); socket6FD = SOCKET_NULL; } + + if (socketUN != SOCKET_NULL) + { + LogVerbose(@"close(socketUN)"); + close(socketUN); + socketUN = SOCKET_NULL; + unlink(socketUrl.path.fileSystemRepresentation); + socketUrl = nil; + } } else { @@ -2763,6 +3312,16 @@ enum GCDAsyncSocketConfig accept6Source = NULL; } + + if (acceptUNSource) + { + LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); + dispatch_source_cancel(acceptUNSource); + + // We never suspend acceptUNSource + + acceptUNSource = NULL; + } if (readSource) { @@ -2788,25 +3347,29 @@ enum GCDAsyncSocketConfig socket4FD = SOCKET_NULL; socket6FD = SOCKET_NULL; + socketUN = SOCKET_NULL; } // If the client has passed the connect/accept method, then the connection has at least begun. // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted); + BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; + BOOL isDeallocating = (flags & kDealloc) ? YES : NO; // Clear stored socket info and all flags (config remains as is) socketFDBytesAvailable = 0; flags = 0; + sslWriteCachedLength = 0; if (shouldCallDelegate) { - if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) + __strong id theDelegate = delegate; + __strong id theSelf = isDeallocating ? nil : self; + + if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) { - __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate socketDidDisconnect:self withError:error]; + [theDelegate socketDidDisconnect:theSelf withError:error]; }}); } } @@ -2816,7 +3379,7 @@ enum GCDAsyncSocketConfig { dispatch_block_t block = ^{ @autoreleasepool { - if (flags & kSocketStarted) + if (self->flags & kSocketStarted) { [self closeWithError:nil]; } @@ -2834,9 +3397,9 @@ enum GCDAsyncSocketConfig { dispatch_async(socketQueue, ^{ @autoreleasepool { - if (flags & kSocketStarted) + if (self->flags & kSocketStarted) { - flags |= (kForbidReadsWrites | kDisconnectAfterReads); + self->flags |= (kForbidReadsWrites | kDisconnectAfterReads); [self maybeClose]; } }}); @@ -2846,9 +3409,9 @@ enum GCDAsyncSocketConfig { dispatch_async(socketQueue, ^{ @autoreleasepool { - if (flags & kSocketStarted) + if (self->flags & kSocketStarted) { - flags |= (kForbidReadsWrites | kDisconnectAfterWrites); + self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites); [self maybeClose]; } }}); @@ -2858,9 +3421,9 @@ enum GCDAsyncSocketConfig { dispatch_async(socketQueue, ^{ @autoreleasepool { - if (flags & kSocketStarted) + if (self->flags & kSocketStarted) { - flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); + self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); [self maybeClose]; } }}); @@ -2914,39 +3477,39 @@ enum GCDAsyncSocketConfig - (NSError *)badConfigError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; } - (NSError *)badParamError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; } -- (NSError *)gaiError:(int)gai_error ++ (NSError *)gaiError:(int)gai_error { NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; } -- (NSError *)errnoErrorWithReason:(NSString *)reason +- (NSError *)errorWithErrno:(int)err reason:(NSString *)reason { - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; + NSString *errMsg = [NSString stringWithUTF8String:strerror(err)]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; + return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo]; } - (NSError *)errnoError { NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; } @@ -2954,7 +3517,7 @@ enum GCDAsyncSocketConfig - (NSError *)sslError:(OSStatus)ssl_error { NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey]; + NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg}; return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; } @@ -2965,7 +3528,7 @@ enum GCDAsyncSocketConfig @"GCDAsyncSocket", [NSBundle mainBundle], @"Attempt to connect to host timed out", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; } @@ -2979,7 +3542,7 @@ enum GCDAsyncSocketConfig @"GCDAsyncSocket", [NSBundle mainBundle], @"Read operation reached set maximum length", nil); - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; } @@ -2993,7 +3556,7 @@ enum GCDAsyncSocketConfig @"GCDAsyncSocket", [NSBundle mainBundle], @"Read operation timed out", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; } @@ -3007,7 +3570,7 @@ enum GCDAsyncSocketConfig @"GCDAsyncSocket", [NSBundle mainBundle], @"Write operation timed out", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; } @@ -3018,14 +3581,14 @@ enum GCDAsyncSocketConfig @"GCDAsyncSocket", [NSBundle mainBundle], @"Socket closed by remote peer", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; } - (NSError *)otherError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; } @@ -3039,7 +3602,7 @@ enum GCDAsyncSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kSocketStarted) ? NO : YES; + result = (self->flags & kSocketStarted) ? NO : YES; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -3055,7 +3618,7 @@ enum GCDAsyncSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kConnected) ? YES : NO; + result = (self->flags & kConnected) ? YES : NO; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -3083,10 +3646,10 @@ enum GCDAsyncSocketConfig dispatch_sync(socketQueue, ^{ @autoreleasepool { - if (socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:socket6FD]; + if (self->socket4FD != SOCKET_NULL) + result = [self connectedHostFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self connectedHostFromSocket6:self->socket6FD]; }}); return result; @@ -3111,16 +3674,39 @@ enum GCDAsyncSocketConfig dispatch_sync(socketQueue, ^{ // No need for autorelease pool - if (socket4FD != SOCKET_NULL) - result = [self connectedPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedPortFromSocket6:socket6FD]; + if (self->socket4FD != SOCKET_NULL) + result = [self connectedPortFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self connectedPortFromSocket6:self->socket6FD]; }); return result; } } +- (NSURL *)connectedUrl +{ + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socketUN != SOCKET_NULL) + return [self connectedUrlFromSocketUN:socketUN]; + + return nil; + } + else + { + __block NSURL *result = nil; + + dispatch_sync(socketQueue, ^{ @autoreleasepool { + + if (self->socketUN != SOCKET_NULL) + result = [self connectedUrlFromSocketUN:self->socketUN]; + }}); + + return result; + } +} + - (NSString *)localHost { if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -3138,10 +3724,10 @@ enum GCDAsyncSocketConfig dispatch_sync(socketQueue, ^{ @autoreleasepool { - if (socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:socket6FD]; + if (self->socket4FD != SOCKET_NULL) + result = [self localHostFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self localHostFromSocket6:self->socket6FD]; }}); return result; @@ -3166,10 +3752,10 @@ enum GCDAsyncSocketConfig dispatch_sync(socketQueue, ^{ // No need for autorelease pool - if (socket4FD != SOCKET_NULL) - result = [self localPortFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localPortFromSocket6:socket6FD]; + if (self->socket4FD != SOCKET_NULL) + result = [self localPortFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self localPortFromSocket6:self->socket6FD]; }); return result; @@ -3288,6 +3874,18 @@ enum GCDAsyncSocketConfig return [[self class] portFromSockaddr6:&sockaddr6]; } +- (NSURL *)connectedUrlFromSocketUN:(int)socketFD +{ + struct sockaddr_un sockaddr; + socklen_t sockaddrlen = sizeof(sockaddr); + + if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) + { + return 0; + } + return [[self class] urlFromSockaddrUN:&sockaddr]; +} + - (NSString *)localHostFromSocket4:(int)socketFD { struct sockaddr_in sockaddr4; @@ -3341,23 +3939,23 @@ enum GCDAsyncSocketConfig __block NSData *result = nil; dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { struct sockaddr_in sockaddr4; socklen_t sockaddr4len = sizeof(sockaddr4); - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + if (getpeername(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) { result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; } } - if (socket6FD != SOCKET_NULL) + if (self->socket6FD != SOCKET_NULL) { struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + if (getpeername(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) { result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; } @@ -3377,23 +3975,23 @@ enum GCDAsyncSocketConfig __block NSData *result = nil; dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { struct sockaddr_in sockaddr4; socklen_t sockaddr4len = sizeof(sockaddr4); - if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) + if (getsockname(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) { result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; } } - if (socket6FD != SOCKET_NULL) + if (self->socket6FD != SOCKET_NULL) { struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); - if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) + if (getsockname(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) { result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; } @@ -3419,7 +4017,7 @@ enum GCDAsyncSocketConfig __block BOOL result = NO; dispatch_sync(socketQueue, ^{ - result = (socket4FD != SOCKET_NULL); + result = (self->socket4FD != SOCKET_NULL); }); return result; @@ -3437,7 +4035,7 @@ enum GCDAsyncSocketConfig __block BOOL result = NO; dispatch_sync(socketQueue, ^{ - result = (socket6FD != SOCKET_NULL); + result = (self->socket6FD != SOCKET_NULL); }); return result; @@ -3455,7 +4053,7 @@ enum GCDAsyncSocketConfig __block BOOL result; dispatch_sync(socketQueue, ^{ - result = (flags & kSocketSecure) ? YES : NO; + result = (self->flags & kSocketSecure) ? YES : NO; }); return result; @@ -3496,7 +4094,8 @@ enum GCDAsyncSocketConfig } if ([components count] > 1 && port == 0) { - long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10); + NSString *temp = [components objectAtIndex:1]; + long portL = strtol([temp UTF8String], NULL, 10); if (portL > 0 && portL <= UINT16_MAX) { @@ -3636,6 +4235,22 @@ enum GCDAsyncSocketConfig if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; } +- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url +{ + NSString *path = url.path; + if (path.length == 0) { + return nil; + } + + struct sockaddr_un nativeAddr; + nativeAddr.sun_family = AF_UNIX; + strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path)); + nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr); + NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; + + return interface; +} + - (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD { readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); @@ -3643,41 +4258,59 @@ enum GCDAsyncSocketConfig // Setup event handlers + __weak GCDAsyncSocket *weakSelf = self; + dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; LogVerbose(@"readEventBlock"); - socketFDBytesAvailable = dispatch_source_get_data(readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable); + strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); + LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable); - if (socketFDBytesAvailable > 0) - [self doReadData]; + if (strongSelf->socketFDBytesAvailable > 0) + [strongSelf doReadData]; else - [self doReadEOF]; + [strongSelf doReadEOF]; + + #pragma clang diagnostic pop }}); dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; LogVerbose(@"writeEventBlock"); - flags |= kSocketCanAcceptBytes; - [self doWriteData]; + strongSelf->flags |= kSocketCanAcceptBytes; + [strongSelf doWriteData]; + + #pragma clang diagnostic pop }}); // Setup cancel handlers __block int socketFDRefCount = 2; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theReadSource = readSource; dispatch_source_t theWriteSource = writeSource; #endif dispatch_source_set_cancel_handler(readSource, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" LogVerbose(@"readCancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(readSource)"); dispatch_release(theReadSource); #endif @@ -3687,13 +4320,17 @@ enum GCDAsyncSocketConfig LogVerbose(@"close(socketFD)"); close(socketFD); } + + #pragma clang diagnostic pop }); dispatch_source_set_cancel_handler(writeSource, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" LogVerbose(@"writeCancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(writeSource)"); dispatch_release(theWriteSource); #endif @@ -3703,6 +4340,8 @@ enum GCDAsyncSocketConfig LogVerbose(@"close(socketFD)"); close(socketFD); } + + #pragma clang diagnostic pop }); // We will not be able to read until data arrives. @@ -3721,17 +4360,14 @@ enum GCDAsyncSocketConfig - (BOOL)usingCFStreamForTLS { #if TARGET_OS_IPHONE - { - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS, - // we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo! - // - // Thus we're not able to use the GCD read/write sources in this particular scenario. - - return YES; - } + + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) + { + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + + return YES; } + #endif return NO; @@ -3739,10 +4375,17 @@ enum GCDAsyncSocketConfig - (BOOL)usingSecureTransportForTLS { + // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) + #if TARGET_OS_IPHONE + + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) { - return ![self usingCFStreamForTLS]; + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + + return NO; } + #endif return YES; @@ -3832,9 +4475,9 @@ enum GCDAsyncSocketConfig LogTrace(); - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) { - [readQueue addObject:packet]; + [self->readQueue addObject:packet]; [self maybeDequeueRead]; } }}); @@ -3875,9 +4518,9 @@ enum GCDAsyncSocketConfig LogTrace(); - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) { - [readQueue addObject:packet]; + [self->readQueue addObject:packet]; [self maybeDequeueRead]; } }}); @@ -3937,9 +4580,9 @@ enum GCDAsyncSocketConfig LogTrace(); - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) { - [readQueue addObject:packet]; + [self->readQueue addObject:packet]; [self maybeDequeueRead]; } }}); @@ -3954,7 +4597,7 @@ enum GCDAsyncSocketConfig dispatch_block_t block = ^{ - if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]]) + if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]]) { // We're not reading anything right now. @@ -3970,10 +4613,10 @@ enum GCDAsyncSocketConfig // If we're reading to data, we of course have no idea when the data will arrive. // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. - NSUInteger done = currentRead->bytesDone; - NSUInteger total = currentRead->readLength; + NSUInteger done = self->currentRead->bytesDone; + NSUInteger total = self->currentRead->readLength; - if (tagPtr != NULL) *tagPtr = currentRead->tag; + if (tagPtr != NULL) *tagPtr = self->currentRead->tag; if (donePtr != NULL) *donePtr = done; if (totalPtr != NULL) *totalPtr = total; @@ -4093,7 +4736,7 @@ enum GCDAsyncSocketConfig return; } -#if TARGET_OS_IPHONE + #if TARGET_OS_IPHONE if ([self usingCFStreamForTLS]) { @@ -4121,8 +4764,7 @@ enum GCDAsyncSocketConfig return; } -#endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE + #endif __block NSUInteger estimatedBytesAvailable = 0; @@ -4138,10 +4780,10 @@ enum GCDAsyncSocketConfig // from the encrypted bytes in the sslPreBuffer. // However, we do know this is an upper bound on the estimation. - estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes]; + estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes]; size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); + SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize); estimatedBytesAvailable += sslInternalBufSize; }; @@ -4187,8 +4829,6 @@ enum GCDAsyncSocketConfig } while (!done && estimatedBytesAvailable > 0); } - -#endif } - (void)doReadData @@ -4244,14 +4884,14 @@ enum GCDAsyncSocketConfig return; } - BOOL hasBytesAvailable; - unsigned long estimatedBytesAvailable; + BOOL hasBytesAvailable = NO; + unsigned long estimatedBytesAvailable = 0; if ([self usingCFStreamForTLS]) { #if TARGET_OS_IPHONE - // Relegated to using CFStream... :( Boo! Give us a full SecureTransport stack Apple! + // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) estimatedBytesAvailable = 0; if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) @@ -4263,8 +4903,6 @@ enum GCDAsyncSocketConfig } else { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - estimatedBytesAvailable = socketFDBytesAvailable; if (flags & kSocketSecure) @@ -4304,8 +4942,6 @@ enum GCDAsyncSocketConfig } hasBytesAvailable = (estimatedBytesAvailable > 0); - - #endif } if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) @@ -4332,16 +4968,12 @@ enum GCDAsyncSocketConfig if (flags & kStartingWriteTLS) { - if ([self usingSecureTransportForTLS]) + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - // We are in the process of a SSL Handshake. // We were waiting for incoming data which has just arrived. [self ssl_continueSSLHandshake]; - - #endif } } else @@ -4361,7 +4993,7 @@ enum GCDAsyncSocketConfig } BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occured + NSError *error = nil; // Error occurred NSUInteger totalBytesReadForCurrentRead = 0; @@ -4455,82 +5087,15 @@ enum GCDAsyncSocketConfig // STEP 2 - READ FROM SOCKET // - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to via socket (end of file) + BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - if (!done && !error && !socketEOF && !waiting && hasBytesAvailable) + if (!done && !error && !socketEOF && hasBytesAvailable) { NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - BOOL readIntoPreBuffer = NO; - NSUInteger bytesToRead; - - if ([self usingCFStreamForTLS]) - { - // Since Apple hasn't made the full power of SecureTransport available on iOS, - // we are relegated to using the slower, less powerful, RunLoop based CFStream API. - // - // This API doesn't tell us how much data is available on the socket to be read. - // If we had that information we could optimize our memory allocations, and sys calls. - // - // But alas... - // So we do it old school, and just read as much data from the socket as we can. - - NSUInteger defaultReadLength = (1024 * 32); - - bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - } - - if (bytesToRead > SIZE_MAX) // NSUInteger may be bigger than size_t (read param 3) - { - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - uint8_t *buffer; - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + currentRead->bytesDone; - } - - // Read data into buffer - + uint8_t *buffer = NULL; size_t bytesRead = 0; if (flags & kSocketSecure) @@ -4539,6 +5104,35 @@ enum GCDAsyncSocketConfig { #if TARGET_OS_IPHONE + // Using CFStream, rather than SecureTransport, for TLS + + NSUInteger defaultReadLength = (1024 * 32); + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); @@ -4565,8 +5159,51 @@ enum GCDAsyncSocketConfig } else { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE + // Using SecureTransport for TLS + // + // We know: + // - how many bytes are available on the socket + // - how many encrypted bytes are sitting in the sslPreBuffer + // - how many decypted bytes are sitting in the sslContext + // + // But we do NOT know: + // - how many encypted bytes are sitting in the sslContext + // + // So we play the regular game of using an upper bound instead. + + NSUInteger defaultReadLength = (1024 * 32); + + if (defaultReadLength < estimatedBytesAvailable) { + defaultReadLength = estimatedBytesAvailable + (1024 * 16); + } + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + // The documentation from Apple states: // // "a read operation might return errSSLWouldBlock, @@ -4583,7 +5220,7 @@ enum GCDAsyncSocketConfig size_t loop_bytesRead = 0; result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)bytesRead); + LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); bytesRead += loop_bytesRead; @@ -4620,13 +5257,61 @@ enum GCDAsyncSocketConfig // Do not modify socketFDBytesAvailable. // It will be updated via the SSLReadFunction(). - - #endif } } else { - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; + // Normal socket operation + + NSUInteger bytesToRead; + + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable + shouldPreBuffer:&readIntoPreBuffer]; + } + else + { + // Read type #1 or #2 + + bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; + } + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); LogVerbose(@"read from socket = %i", (int)result); @@ -4636,7 +5321,7 @@ enum GCDAsyncSocketConfig if (errno == EWOULDBLOCK) waiting = YES; else - error = [self errnoErrorWithReason:@"Error in read() function"]; + error = [self errorWithErrno:errno reason:@"Error in read() function"]; socketFDBytesAvailable = 0; } @@ -4701,27 +5386,27 @@ enum GCDAsyncSocketConfig // Search for the terminating sequence - bytesToRead = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToRead); + NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; + LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy); // Ensure there's room on the read packet's buffer - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; // Copy bytes from prebuffer into read buffer uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + currentRead->bytesDone; - memcpy(readBuf, [preBuffer readBuffer], bytesToRead); + memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToRead]; + [preBuffer didRead:bytesToCopy]; LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); // Update totals - currentRead->bytesDone += bytesToRead; - totalBytesReadForCurrentRead += bytesToRead; + currentRead->bytesDone += bytesToCopy; + totalBytesReadForCurrentRead += bytesToCopy; // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above } @@ -4832,7 +5517,7 @@ enum GCDAsyncSocketConfig } // if (bytesRead > 0) - } // if (!done && !error && !socketEOF && !waiting && hasBytesAvailable) + } // if (!done && !error && !socketEOF && hasBytesAvailable) if (!done && currentRead->readLength == 0 && currentRead->term == nil) @@ -4858,10 +5543,17 @@ enum GCDAsyncSocketConfig else if (totalBytesReadForCurrentRead > 0) { // We're not done read type #2 or #3 yet, but we have read in some bytes + // + // We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is + // possible to reach this point and `waiting` not be set, if the current read's length is + // sufficiently large. In that case, we may have read to some upperbound successfully, but + // that upperbound could be smaller than the desired length. + waiting = YES; + + __strong id theDelegate = delegate; - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) { - __strong id theDelegate = delegate; long theReadTag = currentRead->tag; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -4910,7 +5602,7 @@ enum GCDAsyncSocketConfig [self flushSSLBuffers]; } - BOOL shouldDisconnect; + BOOL shouldDisconnect = NO; NSError *error = nil; if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) @@ -4922,9 +5614,7 @@ enum GCDAsyncSocketConfig if ([self usingSecureTransportForTLS]) { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE error = [self sslError:errSSLClosedAbort]; - #endif } } else if (flags & kReadStreamClosed) @@ -4956,7 +5646,7 @@ enum GCDAsyncSocketConfig // // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; struct pollfd pfd[1]; pfd[0].fd = socketFD; @@ -4974,10 +5664,10 @@ enum GCDAsyncSocketConfig // Notify the delegate that we're going half-duplex - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) { - __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate socketDidCloseReadStream:self]; @@ -5001,7 +5691,6 @@ enum GCDAsyncSocketConfig { if ([self usingSecureTransportForTLS]) { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) { error = [self sslError:sslErrCode]; @@ -5010,7 +5699,6 @@ enum GCDAsyncSocketConfig { error = [self connectionClosedError]; } - #endif } else { @@ -5037,7 +5725,7 @@ enum GCDAsyncSocketConfig NSAssert(currentRead, @"Trying to complete current read when there is no current read."); - NSData *result; + NSData *result = nil; if (currentRead->bufferOwner) { @@ -5068,9 +5756,10 @@ enum GCDAsyncSocketConfig result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; } - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) { - __strong id theDelegate = delegate; GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -5099,20 +5788,34 @@ enum GCDAsyncSocketConfig { readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + __weak GCDAsyncSocket *weakSelf = self; + dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" - [self doReadTimeout]; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doReadTimeout]; + + #pragma clang diagnostic pop }}); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theReadTimer = readTimer; dispatch_source_set_cancel_handler(readTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + LogVerbose(@"dispatch_release(readTimer)"); dispatch_release(theReadTimer); + + #pragma clang diagnostic pop }); #endif - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); dispatch_resume(readTimer); @@ -5128,9 +5831,10 @@ enum GCDAsyncSocketConfig flags |= kReadsPaused; - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) { - __strong id theDelegate = delegate; GCDAsyncReadPacket *theRead = currentRead; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -5141,7 +5845,7 @@ enum GCDAsyncSocketConfig elapsed:theRead->timeout bytesDone:theRead->bytesDone]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { [self doReadTimeoutWithExtension:timeoutExtension]; }}); @@ -5162,7 +5866,7 @@ enum GCDAsyncSocketConfig currentRead->timeout += timeoutExtension; // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); // Unpause reads, and continue @@ -5192,9 +5896,9 @@ enum GCDAsyncSocketConfig LogTrace(); - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) { - [writeQueue addObject:packet]; + [self->writeQueue addObject:packet]; [self maybeDequeueWrite]; } }}); @@ -5209,7 +5913,7 @@ enum GCDAsyncSocketConfig dispatch_block_t block = ^{ - if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) + if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) { // We're not writing anything right now. @@ -5221,10 +5925,10 @@ enum GCDAsyncSocketConfig } else { - NSUInteger done = currentWrite->bytesDone; - NSUInteger total = [currentWrite->buffer length]; + NSUInteger done = self->currentWrite->bytesDone; + NSUInteger total = [self->currentWrite->buffer length]; - if (tagPtr != NULL) *tagPtr = currentWrite->tag; + if (tagPtr != NULL) *tagPtr = self->currentWrite->tag; if (donePtr != NULL) *donePtr = done; if (totalPtr != NULL) *totalPtr = total; @@ -5358,16 +6062,12 @@ enum GCDAsyncSocketConfig if (flags & kStartingReadTLS) { - if ([self usingSecureTransportForTLS]) + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - // We are in the process of a SSL Handshake. // We were waiting for available space in the socket's internal OS buffer to continue writing. [self ssl_continueSSLHandshake]; - - #endif } } else @@ -5432,8 +6132,6 @@ enum GCDAsyncSocketConfig } else { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - // We're going to use the SSLWrite function. // // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) @@ -5532,7 +6230,8 @@ enum GCDAsyncSocketConfig BOOL keepLooping = YES; while (keepLooping) { - size_t sslBytesToWrite = MIN(bytesRemaining, 32768); + const size_t sslMaxBytesToWrite = 32768; + size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); size_t sslBytesWritten = 0; result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); @@ -5563,8 +6262,6 @@ enum GCDAsyncSocketConfig } // while (keepLooping) } // if (hasNewDataToWrite) - - #endif } } else @@ -5573,7 +6270,7 @@ enum GCDAsyncSocketConfig // Writing data directly over raw socket // - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; @@ -5596,7 +6293,7 @@ enum GCDAsyncSocketConfig } else { - error = [self errnoErrorWithReason:@"Error in write() function"]; + error = [self errorWithErrno:errno reason:@"Error in write() function"]; } } else @@ -5644,7 +6341,10 @@ enum GCDAsyncSocketConfig if (!error) { - [self maybeDequeueWrite]; + dispatch_async(socketQueue, ^{ @autoreleasepool{ + + [self maybeDequeueWrite]; + }}); } } else @@ -5652,7 +6352,7 @@ enum GCDAsyncSocketConfig // We were unable to finish writing the data, // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - if (!waiting & !error) + if (!waiting && !error) { // This would be the case if our write was able to accept some data, but not all of it. @@ -5668,9 +6368,10 @@ enum GCDAsyncSocketConfig { // We're not done with the entire write, but we have written some bytes - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) { - __strong id theDelegate = delegate; long theWriteTag = currentWrite->tag; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -5685,7 +6386,7 @@ enum GCDAsyncSocketConfig if (error) { - [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]]; + [self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]]; } // Do not add any code here without first adding a return statement in the error case above. @@ -5697,10 +6398,11 @@ enum GCDAsyncSocketConfig NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); + + __strong id theDelegate = delegate; - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) { - __strong id theDelegate = delegate; long theWriteTag = currentWrite->tag; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -5729,20 +6431,34 @@ enum GCDAsyncSocketConfig { writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + __weak GCDAsyncSocket *weakSelf = self; + dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" - [self doWriteTimeout]; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doWriteTimeout]; + + #pragma clang diagnostic pop }}); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theWriteTimer = writeTimer; dispatch_source_set_cancel_handler(writeTimer, ^{ + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + LogVerbose(@"dispatch_release(writeTimer)"); dispatch_release(theWriteTimer); + + #pragma clang diagnostic pop }); #endif - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); dispatch_resume(writeTimer); @@ -5758,9 +6474,10 @@ enum GCDAsyncSocketConfig flags |= kWritesPaused; - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) { - __strong id theDelegate = delegate; GCDAsyncWritePacket *theWrite = currentWrite; dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -5771,7 +6488,7 @@ enum GCDAsyncSocketConfig elapsed:theWrite->timeout bytesDone:theWrite->bytesDone]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { [self doWriteTimeoutWithExtension:timeoutExtension]; }}); @@ -5792,7 +6509,7 @@ enum GCDAsyncSocketConfig currentWrite->timeout += timeoutExtension; // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); // Unpause writes, and continue @@ -5833,12 +6550,12 @@ enum GCDAsyncSocketConfig dispatch_async(socketQueue, ^{ @autoreleasepool { - if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites)) + if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites)) { - [readQueue addObject:packet]; - [writeQueue addObject:packet]; + [self->readQueue addObject:packet]; + [self->writeQueue addObject:packet]; - flags |= kQueuedTLS; + self->flags |= kQueuedTLS; [self maybeDequeueRead]; [self maybeDequeueWrite]; @@ -5857,38 +6574,24 @@ enum GCDAsyncSocketConfig if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) { - BOOL canUseSecureTransport = YES; + BOOL useSecureTransport = YES; #if TARGET_OS_IPHONE { GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - NSNumber *value; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - if (value && [value boolValue] == NO) - canUseSecureTransport = NO; - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; - if (value && [value boolValue] == YES) - canUseSecureTransport = NO; + NSDictionary *tlsSettings = @{}; + if (tlsPacket) { + tlsSettings = tlsPacket->tlsSettings; + } + NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; + if (value && [value boolValue]) + useSecureTransport = NO; } #endif - if (IS_SECURE_TRANSPORT_AVAILABLE && canUseSecureTransport) + if (useSecureTransport) { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE [self ssl_startTLS]; - #endif } else { @@ -5903,8 +6606,6 @@ enum GCDAsyncSocketConfig #pragma mark Security via SecureTransport //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - - (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength { LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength); @@ -5969,7 +6670,7 @@ enum GCDAsyncSocketConfig { LogVerbose(@"%@: Reading from socket...", THIS_METHOD); - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; BOOL readIntoPreBuffer; size_t bytesToRead; @@ -6089,7 +6790,7 @@ enum GCDAsyncSocketConfig BOOL done = NO; BOOL socketError = NO; - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; ssize_t result = write(socketFD, buffer, bytesToWrite); @@ -6147,17 +6848,25 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, LogTrace(); LogVerbose(@"Starting TLS (via SecureTransport)..."); - + OSStatus status; GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + if (tlsPacket == nil) // Code to quiet the analyzer + { + NSAssert(NO, @"Logic error"); + + [self closeWithError:[self otherError:@"Logic error"]]; + return; + } NSDictionary *tlsSettings = tlsPacket->tlsSettings; // Create SSLContext, and setup IO callbacks and connection ref - BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue]; + NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer]; + BOOL isServer = [isServerNumber boolValue]; - #if TARGET_OS_IPHONE + #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) { if (isServer) sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); @@ -6170,7 +6879,7 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, return; } } - #else + #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) { status = SSLNewContext(isServer, &sslContext); if (status != noErr) @@ -6194,25 +6903,69 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; return; } - + + + NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust]; + if ([shouldManuallyEvaluateTrust boolValue]) + { + if (isServer) + { + [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; + return; + } + + status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; + return; + } + + #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) + + // Note from Apple's documentation: + // + // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. + // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the + // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus + // SSLSetEnableCertVerify is not available on that platform at all. + + status = SSLSetEnableCertVerify(sslContext, NO); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; + return; + } + + #endif + } + // Configure SSLContext from given settings // // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLAllowsAnyRoot - // 3. kCFStreamSSLAllowsExpiredRoots - // 4. kCFStreamSSLValidatesCertificateChain - // 5. kCFStreamSSLAllowsExpiredCertificates - // 6. kCFStreamSSLCertificates - // 7. kCFStreamSSLLevel (GCDAsyncSocketSSLProtocolVersionMin / GCDAsyncSocketSSLProtocolVersionMax) - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) + // 1. kCFStreamSSLPeerName + // 2. kCFStreamSSLCertificates + // 3. GCDAsyncSocketSSLPeerID + // 4. GCDAsyncSocketSSLProtocolVersionMin + // 5. GCDAsyncSocketSSLProtocolVersionMax + // 6. GCDAsyncSocketSSLSessionOptionFalseStart + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord + // 8. GCDAsyncSocketSSLCipherSuites + // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) + // 10. GCDAsyncSocketSSLALPN + // + // Deprecated (throw error): + // 10. kCFStreamSSLAllowsAnyRoot + // 11. kCFStreamSSLAllowsExpiredRoots + // 12. kCFStreamSSLAllowsExpiredCertificates + // 13. kCFStreamSSLValidatesCertificateChain + // 14. kCFStreamSSLLevel - id value; + NSObject *value; // 1. kCFStreamSSLPeerName - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName]; + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName]; if ([value isKindOfClass:[NSString class]]) { NSString *peerName = (NSString *)value; @@ -6227,235 +6980,156 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, return; } } - - // 2. kCFStreamSSLAllowsAnyRoot - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - if (value) + else if (value) { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsAnyRoot"); - #else + NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - BOOL allowsAnyRoot = [value boolValue]; - - status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]]; - return; - } - - #endif + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; + return; } - // 3. kCFStreamSSLAllowsExpiredRoots + // 2. kCFStreamSSLCertificates - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - if (value) + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; + if ([value isKindOfClass:[NSArray class]]) { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredRoots"); - #else + NSArray *certs = (NSArray *)value; - BOOL allowsExpiredRoots = [value boolValue]; - - status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]]; - return; - } - - #endif - } - - // 4. kCFStreamSSLValidatesCertificateChain - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLValidatesCertificateChain"); - #else - - BOOL validatesCertChain = [value boolValue]; - - status = SSLSetEnableCertVerify(sslContext, validatesCertChain); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; - return; - } - - #endif - } - - // 5. kCFStreamSSLAllowsExpiredCertificates - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredCertificates"); - #else - - BOOL allowsExpiredCerts = [value boolValue]; - - status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]]; - return; - } - - #endif - } - - // 6. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates]; - if (value) - { - CFArrayRef certs = (__bridge CFArrayRef)value; - - status = SSLSetCertificate(sslContext, certs); + status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs); if (status != noErr) { [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; return; } } - - // 7. kCFStreamSSLLevel - - #if TARGET_OS_IPHONE + else if (value) { - NSString *sslLevel = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; + NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - NSString *sslMinLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - NSString *sslMaxLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; + return; + } + + // 3. GCDAsyncSocketSSLPeerID + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; + if ([value isKindOfClass:[NSData class]]) + { + NSData *peerIdData = (NSData *)value; - if (sslLevel) + status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); + if (status != noErr) { - if (sslMinLevel || sslMaxLevel) - { - LogWarn(@"kCFStreamSSLLevel security option ignored. Overriden by " - @"GCDAsyncSocketSSLProtocolVersionMin and/or GCDAsyncSocketSSLProtocolVersionMax"); - } - else - { - if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3]) - { - sslMinLevel = sslMaxLevel = @"kSSLProtocol3"; - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1]) - { - sslMinLevel = sslMaxLevel = @"kTLSProtocol1"; - } - else - { - LogWarn(@"Unable to match kCFStreamSSLLevel security option to valid SSL protocol min/max"); - } - } + [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; + return; } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." + @" (You can convert strings to data using a method like" + @" [string dataUsingEncoding:NSUTF8StringEncoding])"); - if (sslMinLevel || sslMaxLevel) + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; + return; + } + + // 4. GCDAsyncSocketSSLProtocolVersionMin + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (minProtocol != kSSLProtocolUnknown) { - OSStatus status1 = noErr; - OSStatus status2 = noErr; - - SSLProtocol (^sslProtocolForString)(NSString*) = ^SSLProtocol (NSString *protocolStr) { - - if ([protocolStr isEqualToString:@"kSSLProtocol3"]) return kSSLProtocol3; - if ([protocolStr isEqualToString:@"kTLSProtocol1"]) return kTLSProtocol1; - if ([protocolStr isEqualToString:@"kTLSProtocol11"]) return kTLSProtocol11; - if ([protocolStr isEqualToString:@"kTLSProtocol12"]) return kTLSProtocol12; - - return kSSLProtocolUnknown; - }; - - SSLProtocol minProtocol = sslProtocolForString(sslMinLevel); - SSLProtocol maxProtocol = sslProtocolForString(sslMaxLevel); - - if (minProtocol != kSSLProtocolUnknown) + status = SSLSetProtocolVersionMin(sslContext, minProtocol); + if (status != noErr) { - status1 = SSLSetProtocolVersionMin(sslContext, minProtocol); - } - if (maxProtocol != kSSLProtocolUnknown) - { - status2 = SSLSetProtocolVersionMax(sslContext, maxProtocol); - } - - if (status1 != noErr || status2 != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMinMax"]]; + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; return; } } } - #else + else if (value) { - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - if (value) + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; + return; + } + + // 5. GCDAsyncSocketSSLProtocolVersionMax + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (maxProtocol != kSSLProtocolUnknown) { - NSString *sslLevel = (NSString *)value; - - OSStatus status1 = noErr; - OSStatus status2 = noErr; - OSStatus status3 = noErr; - - if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2]) + status = SSLSetProtocolVersionMax(sslContext, maxProtocol); + if (status != noErr) { - // kCFStreamSocketSecurityLevelSSLv2: - // - // Specifies that SSL version 2 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3]) - { - // kCFStreamSocketSecurityLevelSSLv3: - // - // Specifies that SSL version 3 be set as the security protocol. - // If SSL version 3 is not available, specifies that SSL version 2 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES); - status3 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1]) - { - // kCFStreamSocketSecurityLevelTLSv1: - // - // Specifies that TLS version 1 be set as the security protocol. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO); - status2 = SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1, YES); - } - else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL]) - { - // kCFStreamSocketSecurityLevelNegotiatedSSL: - // - // Specifies that the highest level security protocol that can be negotiated be used. - - status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES); - } - - if (status1 != noErr || status2 != noErr || status3 != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionEnabled"]]; + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; return; } } } - #endif + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; + return; + } + + // 6. GCDAsyncSocketSSLSessionOptionFalseStart + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; + if ([value isKindOfClass:[NSNumber class]]) + { + NSNumber *falseStart = (NSNumber *)value; + status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [falseStart boolValue]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; + return; + } + + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord + + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; + if ([value isKindOfClass:[NSNumber class]]) + { + NSNumber *oneByteRecord = (NSNumber *)value; + status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [oneByteRecord boolValue]); + if (status != noErr) + { + [self closeWithError: + [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." + @" Value must be of type NSNumber."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; + return; + } // 8. GCDAsyncSocketSSLCipherSuites value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if (value) + if ([value isKindOfClass:[NSArray class]]) { NSArray *cipherSuites = (NSArray *)value; NSUInteger numberCiphers = [cipherSuites count]; @@ -6465,7 +7139,7 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) { NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = [cipherObject shortValue]; + ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue]; } status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); @@ -6475,12 +7149,19 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, return; } } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; + return; + } // 9. GCDAsyncSocketSSLDiffieHellmanParameters #if !TARGET_OS_IPHONE value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if (value) + if ([value isKindOfClass:[NSData class]]) { NSData *diffieHellmanData = (NSData *)value; @@ -6491,7 +7172,120 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, return; } } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; + return; + } #endif + + // 10. kCFStreamSSLCertificates + value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN]; + if ([value isKindOfClass:[NSArray class]]) + { + if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) + { + CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value); + status = SSLSetALPNProtocols(sslContext, protocols); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]]; + return; + } + } + else + { + NSAssert(NO, @"Security option unavailable - GCDAsyncSocketSSLALPN" + @" - iOS 11.0, macOS 10.13 required"); + [self closeWithError:[self otherError:@"Security option unavailable - GCDAsyncSocketSSLALPN"]]; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray."); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]]; + return; + } + + // DEPRECATED checks + + // 10. kCFStreamSSLAllowsAnyRoot + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; + return; + } + + // 11. kCFStreamSSLAllowsExpiredRoots + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; + return; + } + + // 12. kCFStreamSSLValidatesCertificateChain + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; + return; + } + + // 13. kCFStreamSSLAllowsExpiredCertificates + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" + @" - You must use manual trust evaluation"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; + return; + } + + // 14. kCFStreamSSLLevel + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; + #pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" + @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); + + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; + return; + } // Setup the sslPreBuffer // @@ -6511,7 +7305,7 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, [sslPreBuffer didWrite:preBufferLength]; } - sslErrCode = noErr; + sslErrCode = lastSSLHandshakeError = noErr; // Start the SSL Handshake process @@ -6524,9 +7318,13 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, // If the return value is noErr, the session is ready for normal secure communication. // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. + // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the + // server and then call SSLHandshake again to resume the handshake or close the connection + // errSSLPeerBadCert SSL error. // Otherwise, the return value indicates an error code. OSStatus status = SSLHandshake(sslContext); + lastSSLHandshakeError = status; if (status == noErr) { @@ -6537,10 +7335,10 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, flags |= kSocketSecure; - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) { - __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate socketDidSecure:self]; @@ -6553,6 +7351,67 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, [self maybeDequeueRead]; [self maybeDequeueWrite]; } + else if (status == errSSLPeerAuthCompleted) + { + LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); + + __block SecTrustRef trust = NULL; + status = SSLCopyPeerTrust(sslContext, &trust); + if (status != noErr) + { + [self closeWithError:[self sslError:status]]; + return; + } + + int aStateIndex = stateIndex; + dispatch_queue_t theSocketQueue = socketQueue; + + __weak GCDAsyncSocket *weakSelf = self; + + void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + dispatch_async(theSocketQueue, ^{ @autoreleasepool { + + if (trust) { + CFRelease(trust); + trust = NULL; + } + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf) + { + [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; + } + }}); + + #pragma clang diagnostic pop + }}; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; + }}); + } + else + { + if (trust) { + CFRelease(trust); + trust = NULL; + } + + NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," + @" but delegate doesn't implement socket:shouldTrustPeer:"; + + [self closeWithError:[self otherError:msg]]; + return; + } + } else if (status == errSSLWouldBlock) { LogVerbose(@"SSLHandshake continues..."); @@ -6567,7 +7426,35 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, } } -#endif +- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex +{ + LogTrace(); + + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); + + // One of the following is true + // - the socket was disconnected + // - the startTLS operation timed out + // - the completionHandler was already invoked once + + return; + } + + // Increment stateIndex to ensure completionHandler can only be called once. + stateIndex++; + + if (shouldTrust) + { + NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError); + [self ssl_continueSSLHandshake]; + } + else + { + [self closeWithError:[self sslError:errSSLPeerBadCert]]; + } +} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Security via CFStream @@ -6586,10 +7473,10 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, flags |= kSocketSecure; - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) { - __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate socketDidSecure:self]; @@ -6712,19 +7599,72 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, #if TARGET_OS_IPHONE ++ (void)ignore:(id)_ +{} + + (void)startCFStreamThreadIfNeeded { + LogTrace(); + static dispatch_once_t predicate; dispatch_once(&predicate, ^{ - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread) - object:nil]; - [cfstreamThread start]; + cfstreamThreadRetainCount = 0; + cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL); }); + + dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool { + + if (++cfstreamThreadRetainCount == 1) + { + cfstreamThread = [[NSThread alloc] initWithTarget:self + selector:@selector(cfstreamThread:) + object:nil]; + [cfstreamThread start]; + } + }}); } -+ (void)cfstreamThread { @autoreleasepool ++ (void)stopCFStreamThreadIfNeeded +{ + LogTrace(); + + // The creation of the cfstreamThread is relatively expensive. + // So we'd like to keep it available for recycling. + // However, there's a tradeoff here, because it shouldn't remain alive forever. + // So what we're going to do is use a little delay before taking it down. + // This way it can be reused properly in situations where multiple sockets are continually in flux. + + int delayInSeconds = 30; + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { + #pragma clang diagnostic push + #pragma clang diagnostic warning "-Wimplicit-retain-self" + + if (cfstreamThreadRetainCount == 0) + { + LogWarn(@"Logic error concerning cfstreamThread start / stop"); + return_from_block; + } + + if (--cfstreamThreadRetainCount == 0) + { + [cfstreamThread cancel]; // set isCancelled flag + + // wake up the thread + [[self class] performSelector:@selector(ignore:) + onThread:cfstreamThread + withObject:[NSNull null] + waitUntilDone:NO]; + + cfstreamThread = nil; + } + + #pragma clang diagnostic pop + }}); +} + ++ (void)cfstreamThread:(id)unused { @autoreleasepool { [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; @@ -6734,11 +7674,19 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, // So we'll just create a timer that will never fire - unless the server runs for decades. [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] target:self - selector:@selector(doNothingAtAll:) + selector:@selector(ignore:) userInfo:nil repeats:YES]; - [[NSRunLoop currentRunLoop] run]; + NSThread *currentThread = [NSThread currentThread]; + NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; + + BOOL isCancelled = [currentThread isCancelled]; + + while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) + { + isCancelled = [currentThread isCancelled]; + } LogInfo(@"CFStreamThread: Stopped"); }} @@ -6918,7 +7866,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty return YES; } - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; if (socketFD == SOCKET_NULL) { @@ -7013,11 +7961,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty LogVerbose(@"Adding streams to runloop..."); [[self class] startCFStreamThreadIfNeeded]; - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - + dispatch_sync(cfstreamThreadSetupQueue, ^{ + [[self class] performSelector:@selector(scheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + }); flags |= kAddedStreamsToRunLoop; } @@ -7034,11 +7983,14 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty if (flags & kAddedStreamsToRunLoop) { LogVerbose(@"Removing streams from runloop..."); - - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; + + dispatch_sync(cfstreamThreadSetupQueue, ^{ + [[self class] performSelector:@selector(unscheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + }); + [[self class] stopCFStreamThreadIfNeeded]; flags &= ~kAddedStreamsToRunLoop; } @@ -7093,7 +8045,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty __block BOOL result; dispatch_sync(socketQueue, ^{ - result = ((config & kAllowHalfDuplexConnection) == 0); + result = ((self->config & kAllowHalfDuplexConnection) == 0); }); return result; @@ -7110,9 +8062,9 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty dispatch_block_t block = ^{ if (flag) - config &= ~kAllowHalfDuplexConnection; + self->config &= ~kAllowHalfDuplexConnection; else - config |= kAllowHalfDuplexConnection; + self->config |= kAllowHalfDuplexConnection; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -7235,7 +8187,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty { if (![self createReadAndWriteStream]) { - // Error occured creating streams (perhaps socket isn't open) + // Error occurred creating streams (perhaps socket isn't open) return NO; } @@ -7243,9 +8195,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty LogVerbose(@"Enabling backgrouding on socket"); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - +#pragma clang diagnostic pop + if (!r1 || !r2) { return NO; @@ -7297,8 +8252,6 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty #endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - - (SSLContextRef)sslContext { if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -7310,11 +8263,113 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty return sslContext; } -#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Class Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ++ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr +{ + LogTrace(); + + NSMutableArray *addresses = nil; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in nativeAddr4; + nativeAddr4.sin_len = sizeof(struct sockaddr_in); + nativeAddr4.sin_family = AF_INET; + nativeAddr4.sin_port = htons(port); + nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_loopback; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures + + NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + + addresses = [NSMutableArray arrayWithCapacity:2]; + [addresses addObject:address4]; + [addresses addObject:address6]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + NSUInteger capacity = 0; + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { + capacity++; + } + } + + addresses = [NSMutableArray arrayWithCapacity:capacity]; + + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address. + // Wrap the native address structure, and add to results. + + NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address4]; + } + else if (res->ai_family == AF_INET6) + { + // Fixes connection issues with IPv6 + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + + // Found IPv6 address. + // Wrap the native address structure, and add to results. + + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address6]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + if (errPtr) *errPtr = error; + return addresses; +} + (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 { @@ -7350,6 +8405,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty return ntohs(pSockaddr6->sin6_port); } ++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr +{ + NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; + return [NSURL fileURLWithPath:path]; +} + + (NSString *)hostFromAddress:(NSData *)address { NSString *host; @@ -7370,7 +8431,40 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty return 0; } ++ (BOOL)isIPv4Address:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; + + if (sockaddrX->sa_family == AF_INET) { + return YES; + } + } + + return NO; +} + ++ (BOOL)isIPv6Address:(NSData *)address +{ + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; + + if (sockaddrX->sa_family == AF_INET6) { + return YES; + } + } + + return NO; +} + + (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address +{ + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; +} + ++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address { if ([address length] >= sizeof(struct sockaddr)) { @@ -7385,6 +8479,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; + if (afPtr) *afPtr = AF_INET; return YES; } @@ -7398,6 +8493,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; + if (afPtr) *afPtr = AF_INET6; return YES; } diff --git a/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h b/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h old mode 100755 new mode 100644 index 37ca9a9..af327e0 --- a/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h +++ b/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.h @@ -10,16 +10,17 @@ #import #import +#import +#import - +NS_ASSUME_NONNULL_BEGIN extern NSString *const GCDAsyncUdpSocketException; extern NSString *const GCDAsyncUdpSocketErrorDomain; extern NSString *const GCDAsyncUdpSocketQueueName; extern NSString *const GCDAsyncUdpSocketThreadName; -enum GCDAsyncUdpSocketError -{ +typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) { GCDAsyncUdpSocketNoError = 0, // Never used GCDAsyncUdpSocketBadConfigError, // Invalid configuration GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed @@ -27,7 +28,59 @@ enum GCDAsyncUdpSocketError GCDAsyncUdpSocketClosedError, // The socket was closed GCDAsyncUdpSocketOtherError, // Description provided in userInfo }; -typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class GCDAsyncUdpSocket; + +@protocol GCDAsyncUdpSocketDelegate +@optional + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection is successful. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; + +/** + * By design, UDP is a connectionless protocol, and connecting is not needed. + * However, you may optionally choose to connect to a particular host for reasons + * outlined in the documentation for the various connect methods listed above. + * + * This method is called if one of the connect methods are invoked, and the connection fails. + * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; + +/** + * Called when the datagram with the given tag has been sent. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; + +/** + * Called if an error occurs while trying to send a datagram. + * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; + +/** + * Called when the socket has received the requested datagram. +**/ +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data + fromAddress:(NSData *)address + withFilterContext:(nullable id)filterContext; + +/** + * Called when the socket is closed. +**/ +- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; + +@end /** * You may optionally set a receive filter for the socket. @@ -77,7 +130,7 @@ typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError; * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; * **/ -typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id *context); +typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); /** * You may optionally set a send filter for the socket. @@ -125,24 +178,24 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * * The delegate queue and socket queue can optionally be the same. **/ -- (id)init; -- (id)initWithSocketQueue:(dispatch_queue_t)sq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq; -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq; +- (instancetype)init; +- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq; +- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER; #pragma mark Configuration -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; +- (nullable id)delegate; +- (void)setDelegate:(nullable id)delegate; +- (void)synchronouslySetDelegate:(nullable id)delegate; -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue; +- (nullable dispatch_queue_t)delegateQueue; +- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; +- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; +- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; +- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; /** * By default, both IPv4 and IPv6 are enabled. @@ -178,7 +231,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, /** * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. - * The default maximum size is 9216 bytes. + * The default maximum size is 65535 bytes. * * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. @@ -198,12 +251,27 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, - (uint32_t)maxReceiveIPv6BufferSize; - (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; +/** + * Gets/Sets the maximum size of the buffer that will be allocated for send operations. + * The default maximum size is 65535 bytes. + * + * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be + * fragmented, and that’s both expensive and risky (if one fragment goes missing, the + * entire datagram is lost). You are much better off sending a large number of smaller + * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. + * + * You must set it before the sockt is created otherwise it won't work. + * + **/ +- (uint16_t)maxSendBufferSize; +- (void)setMaxSendBufferSize:(uint16_t)max; + /** * User data allows you to associate arbitrary information with the socket. * This data is not used internally in any way. **/ -- (id)userData; -- (void)setUserData:(id)arbitraryUserData; +- (nullable id)userData; +- (void)setUserData:(nullable id)arbitraryUserData; #pragma mark Diagnostics @@ -216,16 +284,16 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: Address info may not be available until after the socket has been binded, connected * or until after data has been sent. **/ -- (NSData *)localAddress; -- (NSString *)localHost; +- (nullable NSData *)localAddress; +- (nullable NSString *)localHost; - (uint16_t)localPort; -- (NSData *)localAddress_IPv4; -- (NSString *)localHost_IPv4; +- (nullable NSData *)localAddress_IPv4; +- (nullable NSString *)localHost_IPv4; - (uint16_t)localPort_IPv4; -- (NSData *)localAddress_IPv6; -- (NSString *)localHost_IPv6; +- (nullable NSData *)localAddress_IPv6; +- (nullable NSString *)localHost_IPv6; - (uint16_t)localPort_IPv6; /** @@ -238,8 +306,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * will not be available unless the socket is explicitly connected to a remote host/port. * If the socket is not connected, these methods will return nil / 0. **/ -- (NSData *)connectedAddress; -- (NSString *)connectedHost; +- (nullable NSData *)connectedAddress; +- (nullable NSString *)connectedHost; - (uint16_t)connectedPort; /** @@ -318,7 +386,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * On success, returns YES. * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. **/ -- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; /** * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. @@ -417,10 +485,33 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * On success, returns YES. * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. **/ -- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; - (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; +- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; + +/** + * Send multicast on a specified interface. + * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1"). + * For IPv6, interface should be the a network interface name (eg @"en0"). + * + * On success, returns YES. + * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. +**/ + +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr; + +#pragma mark Reuse Port + +/** + * By default, only one socket can be bound to a given IP address + port at a time. + * To enable multiple processes to simultaneously bind to the same address+port, + * you need to enable this functionality in the socket. All processes that wish to + * use the address+port simultaneously must all enable reuse port on the socket + * bound to that port. + **/ +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; #pragma mark Broadcast @@ -545,7 +636,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * If data is nil or zero-length, this method does nothing. * If passing NSMutableData, please read the thread-safety notice below. * - * @param address + * @param remoteAddr * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). * * @param timeout @@ -596,7 +687,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), * passing YES for the isAsynchronous parameter. **/ -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; /** * The receive filter can be run via dispatch_async or dispatch_sync. @@ -611,8 +702,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. * For example, you can't query properties on the socket. **/ -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue +- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous; #pragma mark Receiving @@ -728,7 +819,7 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), * passing YES for the isAsynchronous parameter. **/ -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; /** * The receive filter can be run via dispatch_async or dispatch_sync. @@ -743,8 +834,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. * For example, you can't query properties on the socket. **/ -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue +- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock + withQueue:(nullable dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous; #pragma mark Closing @@ -896,8 +987,8 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * However, if you need one for any reason, * these methods are a convenient way to get access to a safe instance of one. **/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; +- (nullable CFReadStreamRef)readStream; +- (nullable CFWriteStreamRef)writeStream; /** * This method is only available from within the context of a performBlock: invocation. @@ -930,66 +1021,16 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, * Extracting host/port/family information from raw address data. **/ -+ (NSString *)hostFromAddress:(NSData *)address; ++ (nullable NSString *)hostFromAddress:(NSData *)address; + (uint16_t)portFromAddress:(NSData *)address; + (int)familyFromAddress:(NSData *)address; + (BOOL)isIPv4Address:(NSData *)address; + (BOOL)isIPv6Address:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncUdpSocketDelegate -@optional - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection is successful. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection fails. - * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error; - -/** - * Called when the datagram with the given tag has been sent. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; - -/** - * Called if an error occurs while trying to send a datagram. - * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error; - -/** - * Called when the socket has received the requested datagram. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data - fromAddress:(NSData *)address - withFilterContext:(id)filterContext; - -/** - * Called when the socket is closed. -**/ -- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; ++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; @end +NS_ASSUME_NONNULL_END diff --git a/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.m b/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.m index 77a7750..b0c59c3 100755 --- a/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.m +++ b/Ifish/AsyncSocket/GCD/GCDAsyncUdpSocket.m @@ -15,32 +15,6 @@ // For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC #endif -/** - * Does ARC support support GCD objects? - * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ -**/ -#if TARGET_OS_IPHONE - - // Compiling for iOS - - #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else // iOS 5.X or earlier - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 - #endif - -#else - - // Compiling for Mac OS X - - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later - #define NEEDS_DISPATCH_RETAIN_RELEASE 0 - #else - #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier - #endif - -#endif - #if TARGET_OS_IPHONE #import #import @@ -60,7 +34,7 @@ // Logging Enabled - See log level below // Logging uses the CocoaLumberjack framework (which is also GCD based). -// http://code.google.com/p/cocoalumberjack/ +// https://github.com/robbiehanson/CocoaLumberjack // // It allows us to do a lot of logging without significantly slowing down the code. #import "DDLog.h" @@ -172,7 +146,11 @@ enum GCDAsyncUdpSocketConfig @interface GCDAsyncUdpSocket () { - id delegate; +#if __has_feature(objc_arc_weak) + __weak id delegate; +#else + __unsafe_unretained id delegate; +#endif dispatch_queue_t delegateQueue; GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; @@ -188,6 +166,8 @@ enum GCDAsyncUdpSocketConfig uint16_t max4ReceiveSize; uint32_t max6ReceiveSize; + + uint16_t maxSendSize; int socket4FD; int socket6FD; @@ -273,7 +253,7 @@ enum GCDAsyncUdpSocketConfig #if TARGET_OS_IPHONE // Forward declaration -+ (void)listenerThread; ++ (void)listenerThread:(id)unused; #endif @end @@ -301,13 +281,20 @@ enum GCDAsyncUdpSocketConfig int addressFamily; } -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; @end @implementation GCDAsyncUdpSendPacket -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i +// Cover the superclass' designated initializer +- (instancetype)init NS_UNAVAILABLE +{ + NSAssert(0, @"Use the designated initializer"); + return nil; +} + +- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i { if ((self = [super init])) { @@ -337,13 +324,13 @@ enum GCDAsyncUdpSocketConfig NSError *error; } -- (id)init; +- (instancetype)init NS_DESIGNATED_INITIALIZER; @end @implementation GCDAsyncUdpSpecialPacket -- (id)init +- (instancetype)init { self = [super init]; return self; @@ -358,28 +345,28 @@ enum GCDAsyncUdpSocketConfig @implementation GCDAsyncUdpSocket -- (id)init +- (instancetype)init { LogTrace(); return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; } -- (id)initWithSocketQueue:(dispatch_queue_t)sq +- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq { LogTrace(); return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq { LogTrace(); return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; } -- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq +- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq { LogTrace(); @@ -390,14 +377,16 @@ enum GCDAsyncUdpSocketConfig if (dq) { delegateQueue = dq; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(delegateQueue); #endif } - max4ReceiveSize = 9216; - max6ReceiveSize = 9216; + max4ReceiveSize = 65535; + max6ReceiveSize = 65535; + maxSendSize = 65535; + socket4FD = SOCKET_NULL; socket6FD = SOCKET_NULL; @@ -411,7 +400,7 @@ enum GCDAsyncUdpSocketConfig @"The given socketQueue parameter must not be a concurrent queue."); socketQueue = sq; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(socketQueue); #endif } @@ -475,12 +464,12 @@ enum GCDAsyncUdpSocketConfig } delegate = nil; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (delegateQueue) dispatch_release(delegateQueue); #endif delegateQueue = NULL; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC if (socketQueue) dispatch_release(socketQueue); #endif socketQueue = NULL; @@ -492,7 +481,7 @@ enum GCDAsyncUdpSocketConfig #pragma mark Configuration //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (id)delegate +- (id)delegate { if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -503,17 +492,17 @@ enum GCDAsyncUdpSocketConfig __block id result = nil; dispatch_sync(socketQueue, ^{ - result = delegate; + result = self->delegate; }); return result; } } -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously +- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ - delegate = newDelegate; + self->delegate = newDelegate; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -527,12 +516,12 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate +- (void)setDelegate:(id)newDelegate { [self setDelegate:newDelegate synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate +- (void)synchronouslySetDelegate:(id)newDelegate { [self setDelegate:newDelegate synchronously:YES]; } @@ -548,7 +537,7 @@ enum GCDAsyncUdpSocketConfig __block dispatch_queue_t result = NULL; dispatch_sync(socketQueue, ^{ - result = delegateQueue; + result = self->delegateQueue; }); return result; @@ -559,12 +548,12 @@ enum GCDAsyncUdpSocketConfig { dispatch_block_t block = ^{ - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); if (newDelegateQueue) dispatch_retain(newDelegateQueue); #endif - delegateQueue = newDelegateQueue; + self->delegateQueue = newDelegateQueue; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -588,7 +577,7 @@ enum GCDAsyncUdpSocketConfig [self setDelegateQueue:newDelegateQueue synchronously:YES]; } -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr +- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr { if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -601,8 +590,8 @@ enum GCDAsyncUdpSocketConfig __block dispatch_queue_t dqPtr = NULL; dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; + dPtr = self->delegate; + dqPtr = self->delegateQueue; }); if (delegatePtr) *delegatePtr = dPtr; @@ -610,18 +599,18 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { dispatch_block_t block = ^{ - delegate = newDelegate; + self->delegate = newDelegate; - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); + #if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); if (newDelegateQueue) dispatch_retain(newDelegateQueue); #endif - delegateQueue = newDelegateQueue; + self->delegateQueue = newDelegateQueue; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { @@ -635,12 +624,12 @@ enum GCDAsyncUdpSocketConfig } } -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; } -- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue +- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; } @@ -653,7 +642,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = ((config & kIPv4Disabled) == 0); + result = ((self->config & kIPv4Disabled) == 0); }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -673,9 +662,9 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); if (flag) - config &= ~kIPv4Disabled; + self->config &= ~kIPv4Disabled; else - config |= kIPv4Disabled; + self->config |= kIPv4Disabled; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -692,7 +681,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = ((config & kIPv6Disabled) == 0); + result = ((self->config & kIPv6Disabled) == 0); }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -712,9 +701,9 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); if (flag) - config &= ~kIPv6Disabled; + self->config &= ~kIPv6Disabled; else - config |= kIPv6Disabled; + self->config |= kIPv6Disabled; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -728,7 +717,7 @@ enum GCDAsyncUdpSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kPreferIPv4) ? YES : NO; + result = (self->config & kPreferIPv4) ? YES : NO; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -744,7 +733,7 @@ enum GCDAsyncUdpSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & kPreferIPv6) ? YES : NO; + result = (self->config & kPreferIPv6) ? YES : NO; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -760,7 +749,7 @@ enum GCDAsyncUdpSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (config & (kPreferIPv4 | kPreferIPv6)) == 0; + result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -777,8 +766,8 @@ enum GCDAsyncUdpSocketConfig LogTrace(); - config |= kPreferIPv4; - config &= ~kPreferIPv6; + self->config |= kPreferIPv4; + self->config &= ~kPreferIPv6; }; @@ -794,8 +783,8 @@ enum GCDAsyncUdpSocketConfig LogTrace(); - config &= ~kPreferIPv4; - config |= kPreferIPv6; + self->config &= ~kPreferIPv4; + self->config |= kPreferIPv6; }; @@ -811,8 +800,8 @@ enum GCDAsyncUdpSocketConfig LogTrace(); - config &= ~kPreferIPv4; - config &= ~kPreferIPv6; + self->config &= ~kPreferIPv4; + self->config &= ~kPreferIPv6; }; @@ -828,7 +817,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = max4ReceiveSize; + result = self->max4ReceiveSize; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -845,7 +834,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - max4ReceiveSize = max; + self->max4ReceiveSize = max; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -860,7 +849,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = max6ReceiveSize; + result = self->max6ReceiveSize; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -877,7 +866,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - max6ReceiveSize = max; + self->max6ReceiveSize = max; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -886,6 +875,37 @@ enum GCDAsyncUdpSocketConfig dispatch_async(socketQueue, block); } +- (void)setMaxSendBufferSize:(uint16_t)max +{ + dispatch_block_t block = ^{ + + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + + self->maxSendSize = max; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); +} + +- (uint16_t)maxSendBufferSize +{ + __block uint16_t result = 0; + + dispatch_block_t block = ^{ + + result = self->maxSendSize; + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} - (id)userData { @@ -893,7 +913,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = userData; + result = self->userData; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -908,9 +928,9 @@ enum GCDAsyncUdpSocketConfig { dispatch_block_t block = ^{ - if (userData != arbitraryUserData) + if (self->userData != arbitraryUserData) { - userData = arbitraryUserData; + self->userData = arbitraryUserData; } }; @@ -928,9 +948,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) { - id theDelegate = delegate; NSData *address = [anAddress copy]; // In case param is NSMutableData dispatch_async(delegateQueue, ^{ @autoreleasepool { @@ -944,10 +964,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didNotConnect:error]; @@ -959,10 +978,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didSendDataWithTag:tag]; @@ -974,10 +992,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; @@ -991,10 +1008,9 @@ enum GCDAsyncUdpSocketConfig SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); - if (delegateQueue && [delegate respondsToSelector:selector]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:selector]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; @@ -1006,10 +1022,9 @@ enum GCDAsyncUdpSocketConfig { LogTrace(); - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) { - id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { [theDelegate udpSocketDidClose:self withError:error]; @@ -1023,7 +1038,7 @@ enum GCDAsyncUdpSocketConfig - (NSError *)badConfigError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketBadConfigError @@ -1032,7 +1047,7 @@ enum GCDAsyncUdpSocketConfig - (NSError *)badParamError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketBadParamError @@ -1042,7 +1057,7 @@ enum GCDAsyncUdpSocketConfig - (NSError *)gaiError:(int)gai_error { NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; } @@ -1053,10 +1068,10 @@ enum GCDAsyncUdpSocketConfig NSDictionary *userInfo; if (reason) - userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; + userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; else - userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, nil]; + userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; } @@ -1075,7 +1090,7 @@ enum GCDAsyncUdpSocketConfig @"GCDAsyncUdpSocket", [NSBundle mainBundle], @"Send operation timed out", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketSendTimeoutError @@ -1088,14 +1103,14 @@ enum GCDAsyncUdpSocketConfig @"GCDAsyncUdpSocket", [NSBundle mainBundle], @"Socket closed", nil); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; } - (NSError *)otherError:(NSString *)errMsg { - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketOtherError @@ -1226,9 +1241,17 @@ enum GCDAsyncUdpSocketConfig } else if (res->ai_family == AF_INET6) { - // Found IPv6 address - // Wrap the native address structure and add to list - + + // Fixes connection issues with IPv6, it is the same solution for udp socket. + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + // Found IPv6 address + // Wrap the native address structure and add to list [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; } } @@ -1241,7 +1264,7 @@ enum GCDAsyncUdpSocketConfig } } - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { completionBlock(addresses, error); }}); @@ -1470,7 +1493,7 @@ enum GCDAsyncUdpSocketConfig { // IPv4 - struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; + struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; if (strcmp(cursor->ifa_name, iface) == 0) { @@ -1503,7 +1526,7 @@ enum GCDAsyncUdpSocketConfig { // IPv6 - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; if (strcmp(cursor->ifa_name, iface) == 0) { @@ -1604,8 +1627,8 @@ enum GCDAsyncUdpSocketConfig return NO; } - const struct sockaddr_in *sSockaddr4 = (struct sockaddr_in *)[someAddr4 bytes]; - const struct sockaddr_in *cSockaddr4 = (struct sockaddr_in *)[cachedConnectedAddress bytes]; + const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; + const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) { @@ -1630,8 +1653,8 @@ enum GCDAsyncUdpSocketConfig return NO; } - const struct sockaddr_in6 *sSockaddr6 = (struct sockaddr_in6 *)[someAddr6 bytes]; - const struct sockaddr_in6 *cSockaddr6 = (struct sockaddr_in6 *)[cachedConnectedAddress bytes]; + const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; + const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) { @@ -1653,7 +1676,7 @@ enum GCDAsyncUdpSocketConfig return 0; int result = 0; - struct sockaddr_in *ifaceAddr = (struct sockaddr_in *)[interfaceAddr4 bytes]; + const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; struct ifaddrs *addrs; const struct ifaddrs *cursor; @@ -1667,7 +1690,7 @@ enum GCDAsyncUdpSocketConfig { // IPv4 - struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) { @@ -1693,7 +1716,7 @@ enum GCDAsyncUdpSocketConfig return 0; int result = 0; - struct sockaddr_in6 *ifaceAddr = (struct sockaddr_in6 *)[interfaceAddr6 bytes]; + const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; struct ifaddrs *addrs; const struct ifaddrs *cursor; @@ -1707,7 +1730,7 @@ enum GCDAsyncUdpSocketConfig { // IPv6 - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) { @@ -1740,22 +1763,22 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"send4EventBlock"); LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); - flags |= kSock4CanAcceptBytes; + self->flags |= kSock4CanAcceptBytes; // If we're ready to send data, do so immediately. // Otherwise pause the send source or it will continue to fire over and over again. - if (currentSend == nil) + if (self->currentSend == nil) { LogVerbose(@"Nothing to send"); [self suspendSend4Source]; } - else if (currentSend->resolveInProgress) + else if (self->currentSend->resolveInProgress) { LogVerbose(@"currentSend - waiting for address resolve"); [self suspendSend4Source]; } - else if (currentSend->filterInProgress) + else if (self->currentSend->filterInProgress) { LogVerbose(@"currentSend - waiting on sendFilter"); [self suspendSend4Source]; @@ -1771,10 +1794,10 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"receive4EventBlock"); - socket4FDBytesAvailable = dispatch_source_get_data(receive4Source); + self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); - if (socket4FDBytesAvailable > 0) + if (self->socket4FDBytesAvailable > 0) [self doReceive]; else [self doReceiveEOF]; @@ -1787,7 +1810,7 @@ enum GCDAsyncUdpSocketConfig int theSocketFD = socket4FD; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theSendSource = send4Source; dispatch_source_t theReceiveSource = receive4Source; #endif @@ -1796,7 +1819,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"send4CancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(send4Source)"); dispatch_release(theSendSource); #endif @@ -1812,7 +1835,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"receive4CancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(receive4Source)"); dispatch_release(theReceiveSource); #endif @@ -1851,22 +1874,22 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"send6EventBlock"); LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); - flags |= kSock6CanAcceptBytes; + self->flags |= kSock6CanAcceptBytes; // If we're ready to send data, do so immediately. // Otherwise pause the send source or it will continue to fire over and over again. - if (currentSend == nil) + if (self->currentSend == nil) { LogVerbose(@"Nothing to send"); [self suspendSend6Source]; } - else if (currentSend->resolveInProgress) + else if (self->currentSend->resolveInProgress) { LogVerbose(@"currentSend - waiting for address resolve"); [self suspendSend6Source]; } - else if (currentSend->filterInProgress) + else if (self->currentSend->filterInProgress) { LogVerbose(@"currentSend - waiting on sendFilter"); [self suspendSend6Source]; @@ -1882,10 +1905,10 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"receive6EventBlock"); - socket6FDBytesAvailable = dispatch_source_get_data(receive6Source); + self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); - if (socket6FDBytesAvailable > 0) + if (self->socket6FDBytesAvailable > 0) [self doReceive]; else [self doReceiveEOF]; @@ -1898,7 +1921,7 @@ enum GCDAsyncUdpSocketConfig int theSocketFD = socket6FD; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_source_t theSendSource = send6Source; dispatch_source_t theReceiveSource = receive6Source; #endif @@ -1907,7 +1930,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"send6CancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(send6Source)"); dispatch_release(theSendSource); #endif @@ -1923,7 +1946,7 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"receive6CancelBlock"); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC LogVerbose(@"dispatch_release(receive6Source)"); dispatch_release(theReceiveSource); #endif @@ -1947,7 +1970,7 @@ enum GCDAsyncUdpSocketConfig flags |= kReceive6SourceSuspended; } -- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError **)errPtr +- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr { LogTrace(); @@ -2004,6 +2027,39 @@ enum GCDAsyncUdpSocketConfig close(socketFD); return SOCKET_NULL; } + + /** + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * The default maximum size of the UDP buffer in iOS is 9216 bytes. + * + * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and + * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) + * + * + * Enlarge the maximum size of UDP packet. + * I can not ensure the protocol type now so that the max size is set to 65535 :) + **/ + + status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + return socketFD; }; @@ -2265,7 +2321,7 @@ enum GCDAsyncUdpSocketConfig LogWarn(@"Error in getsockname: %@", [self errnoError]); } } - else if (socketFamily == AF_INET) + else if (socketFamily == AF_INET6) { struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); @@ -2339,15 +2395,15 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalAddress4; + result = self->cachedLocalAddress4; } else { [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalAddress6; + result = self->cachedLocalAddress6; } }; @@ -2366,15 +2422,15 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalHost4; + result = self->cachedLocalHost4; } else { [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalHost6; + result = self->cachedLocalHost6; } }; @@ -2392,15 +2448,15 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalPort4; + result = self->cachedLocalPort4; } else { [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalPort6; + result = self->cachedLocalPort6; } }; @@ -2419,7 +2475,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalAddress4; + result = self->cachedLocalAddress4; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2437,7 +2493,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalHost4; + result = self->cachedLocalHost4; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2455,7 +2511,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalPort4; + result = self->cachedLocalPort4; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2473,7 +2529,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalAddress6; + result = self->cachedLocalAddress6; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2491,7 +2547,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalHost6; + result = self->cachedLocalHost6; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2509,7 +2565,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalPort6; + result = self->cachedLocalPort6; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2583,7 +2639,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedAddress; + result = self->cachedConnectedAddress; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2601,7 +2657,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedHost; + result = self->cachedConnectedHost; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2619,7 +2675,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedPort; + result = self->cachedConnectedPort; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2635,7 +2691,7 @@ enum GCDAsyncUdpSocketConfig __block BOOL result = NO; dispatch_block_t block = ^{ - result = (flags & kDidConnect) ? YES : NO; + result = (self->flags & kDidConnect) ? YES : NO; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2652,7 +2708,7 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - result = (flags & kDidCreateSockets) ? NO : YES; + result = (self->flags & kDidCreateSockets) ? NO : YES; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -2669,9 +2725,9 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if (flags & kDidCreateSockets) + if (self->flags & kDidCreateSockets) { - result = (socket4FD != SOCKET_NULL); + result = (self->socket4FD != SOCKET_NULL); } else { @@ -2693,9 +2749,9 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if (flags & kDidCreateSockets) + if (self->flags & kDidCreateSockets) { - result = (socket6FD != SOCKET_NULL); + result = (self->socket6FD != SOCKET_NULL); } else { @@ -2796,8 +2852,8 @@ enum GCDAsyncUdpSocketConfig return_from_block; } - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; if (isIPv4Disabled && (interface6 == nil)) { @@ -2822,7 +2878,7 @@ enum GCDAsyncUdpSocketConfig // Create the socket(s) if needed - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) { @@ -2836,7 +2892,7 @@ enum GCDAsyncUdpSocketConfig if (useIPv4) { - int status = bind(socket4FD, (struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); + int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); if (status == -1) { [self closeSockets]; @@ -2850,7 +2906,7 @@ enum GCDAsyncUdpSocketConfig if (useIPv6) { - int status = bind(socket6FD, (struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); + int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); if (status == -1) { [self closeSockets]; @@ -2864,10 +2920,10 @@ enum GCDAsyncUdpSocketConfig // Update flags - flags |= kDidBind; + self->flags |= kDidBind; - if (!useIPv4) flags |= kIPv4Deactivated; - if (!useIPv6) flags |= kIPv6Deactivated; + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; result = YES; @@ -2916,8 +2972,8 @@ enum GCDAsyncUdpSocketConfig NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; if (isIPv4Disabled && localAddr4) { @@ -2942,7 +2998,7 @@ enum GCDAsyncUdpSocketConfig // Create the socket(s) if needed - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) { @@ -2958,7 +3014,7 @@ enum GCDAsyncUdpSocketConfig [[self class] hostFromAddress:localAddr4], [[self class] portFromAddress:localAddr4]); - int status = bind(socket4FD, (struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); + int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); if (status == -1) { [self closeSockets]; @@ -2975,7 +3031,7 @@ enum GCDAsyncUdpSocketConfig [[self class] hostFromAddress:localAddr6], [[self class] portFromAddress:localAddr6]); - int status = bind(socket6FD, (struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); + int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); if (status == -1) { [self closeSockets]; @@ -2989,10 +3045,10 @@ enum GCDAsyncUdpSocketConfig // Update flags - flags |= kDidBind; + self->flags |= kDidBind; - if (!useIPv4) flags |= kIPv4Deactivated; - if (!useIPv6) flags |= kIPv6Deactivated; + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; result = YES; @@ -3079,7 +3135,7 @@ enum GCDAsyncUdpSocketConfig // Create the socket(s) if needed - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { if (![self createSockets:&err]) { @@ -3112,9 +3168,9 @@ enum GCDAsyncUdpSocketConfig // Updates flags, add connect packet to send queue, and pump send queue - flags |= kConnecting; + self->flags |= kConnecting; - [sendQueue addObject:packet]; + [self->sendQueue addObject:packet]; [self maybeDequeueSend]; result = YES; @@ -3160,7 +3216,7 @@ enum GCDAsyncUdpSocketConfig // Create the socket(s) if needed - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { if (![self createSockets:&err]) { @@ -3179,9 +3235,9 @@ enum GCDAsyncUdpSocketConfig // Updates flags, add connect packet to send queue, and pump send queue - flags |= kConnecting; + self->flags |= kConnecting; - [sendQueue addObject:packet]; + [self->sendQueue addObject:packet]; [self maybeDequeueSend]; result = YES; @@ -3271,7 +3327,7 @@ enum GCDAsyncUdpSocketConfig LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - int status = connect(socket4FD, (struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); + int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); if (status != 0) { if (errPtr) @@ -3291,7 +3347,7 @@ enum GCDAsyncUdpSocketConfig LogTrace(); NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - int status = connect(socket6FD, (struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); + int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); if (status != 0) { if (errPtr) @@ -3411,16 +3467,16 @@ enum GCDAsyncUdpSocketConfig // Perform join - if ((socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) + if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) { - const struct sockaddr_in *nativeGroup = (struct sockaddr_in *)[groupAddr4 bytes]; - const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; + const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; struct ip_mreq imreq; imreq.imr_multiaddr = nativeGroup->sin_addr; imreq.imr_interface = nativeIface->sin_addr; - int status = setsockopt(socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); + int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); if (status != 0) { err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; @@ -3433,15 +3489,15 @@ enum GCDAsyncUdpSocketConfig result = YES; } - else if ((socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) + else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) { - const struct sockaddr_in6 *nativeGroup = (struct sockaddr_in6 *)[groupAddr6 bytes]; + const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; struct ipv6_mreq imreq; imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; - int status = setsockopt(socket6FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq)); if (status != 0) { err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; @@ -3475,6 +3531,185 @@ enum GCDAsyncUdpSocketConfig return result; } +- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr4 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by IP address."; + err = [self badParamError:msg]; + return_from_block; + } + + if (self->socket4FD != SOCKET_NULL) { + const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + struct in_addr interface_addr = nativeIface->sin_addr; + int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + result = YES; + } + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + // Convert interface to address + + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; + + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + + if (interfaceAddr6 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; + err = [self badParamError:msg]; + return_from_block; + } + + if ((self->socket6FD != SOCKET_NULL)) { + uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; + int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Reuse port +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr +{ + __block BOOL result = NO; + __block NSError *err = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + if (![self preOp:&err]) + { + return_from_block; + } + + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } + + int value = flag ? 1 : 0; + if (self->socket4FD != SOCKET_NULL) + { + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + if (self->socket6FD != SOCKET_NULL) + { + int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + result = YES; + } + + }}; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + if (errPtr) + *errPtr = err; + + return result; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Broadcast //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3491,7 +3726,7 @@ enum GCDAsyncUdpSocketConfig return_from_block; } - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { if (![self createSockets:&err]) { @@ -3499,10 +3734,10 @@ enum GCDAsyncUdpSocketConfig } } - if (socket4FD != SOCKET_NULL) + if (self->socket4FD != SOCKET_NULL) { int value = flag ? 1 : 0; - int error = setsockopt(socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); + int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); if (error) { @@ -3547,12 +3782,14 @@ enum GCDAsyncUdpSocketConfig LogWarn(@"Ignoring attempt to send nil/empty data."); return; } + + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; dispatch_async(socketQueue, ^{ @autoreleasepool { - [sendQueue addObject:packet]; + [self->sendQueue addObject:packet]; [self maybeDequeueSend]; }}); @@ -3586,7 +3823,7 @@ enum GCDAsyncUdpSocketConfig packet->resolvedAddresses = addresses; packet->resolveError = error; - if (packet == currentSend) + if (packet == self->currentSend) { LogVerbose(@"currentSend - address resolved"); [self doPreSend]; @@ -3595,7 +3832,7 @@ enum GCDAsyncUdpSocketConfig dispatch_async(socketQueue, ^{ @autoreleasepool { - [sendQueue addObject:packet]; + [self->sendQueue addObject:packet]; [self maybeDequeueSend]; }}); @@ -3618,7 +3855,7 @@ enum GCDAsyncUdpSocketConfig dispatch_async(socketQueue, ^{ @autoreleasepool { - [sendQueue addObject:packet]; + [self->sendQueue addObject:packet]; [self maybeDequeueSend]; }}); } @@ -3641,20 +3878,20 @@ enum GCDAsyncUdpSocketConfig newFilterBlock = [filterBlock copy]; newFilterQueue = filterQueue; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(newFilterQueue); #endif } dispatch_block_t block = ^{ - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (sendFilterQueue) dispatch_release(sendFilterQueue); + #if !OS_OBJECT_USE_OBJC + if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); #endif - sendFilterBlock = newFilterBlock; - sendFilterQueue = newFilterQueue; - sendFilterAsync = isAsynchronous; + self->sendFilterBlock = newFilterBlock; + self->sendFilterQueue = newFilterQueue; + self->sendFilterAsync = isAsynchronous; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -3833,12 +4070,12 @@ enum GCDAsyncUdpSocketConfig dispatch_async(sendFilterQueue, ^{ @autoreleasepool { - BOOL allowed = sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); + BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { sendPacket->filterInProgress = NO; - if (sendPacket == currentSend) + if (sendPacket == self->currentSend) { if (allowed) { @@ -3848,7 +4085,7 @@ enum GCDAsyncUdpSocketConfig { LogVerbose(@"currentSend - silently dropped by sendFilter"); - [self notifyDidSendDataWithTag:currentSend->tag]; + [self notifyDidSendDataWithTag:self->currentSend->tag]; [self endCurrentSend]; [self maybeDequeueSend]; } @@ -3864,7 +4101,7 @@ enum GCDAsyncUdpSocketConfig dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { - allowed = sendFilterBlock(currentSend->buffer, currentSend->address, currentSend->tag); + allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag); }}); if (allowed) @@ -4014,7 +4251,7 @@ enum GCDAsyncUdpSocketConfig if (sendTimer) { dispatch_source_cancel(sendTimer); - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_release(sendTimer); #endif sendTimer = NULL; @@ -4072,9 +4309,9 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if ((flags & kReceiveOnce) == 0) + if ((self->flags & kReceiveOnce) == 0) { - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { NSString *msg = @"Must bind socket before you can receive data. " @"You can do this explicitly via bind, or implicitly via connect or by sending data."; @@ -4083,10 +4320,10 @@ enum GCDAsyncUdpSocketConfig return_from_block; } - flags |= kReceiveOnce; // Enable - flags &= ~kReceiveContinuous; // Disable + self->flags |= kReceiveOnce; // Enable + self->flags &= ~kReceiveContinuous; // Disable - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { [self doReceive]; }}); @@ -4118,9 +4355,9 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - if ((flags & kReceiveContinuous) == 0) + if ((self->flags & kReceiveContinuous) == 0) { - if ((flags & kDidCreateSockets) == 0) + if ((self->flags & kDidCreateSockets) == 0) { NSString *msg = @"Must bind socket before you can receive data. " @"You can do this explicitly via bind, or implicitly via connect or by sending data."; @@ -4129,10 +4366,10 @@ enum GCDAsyncUdpSocketConfig return_from_block; } - flags |= kReceiveContinuous; // Enable - flags &= ~kReceiveOnce; // Disable + self->flags |= kReceiveContinuous; // Enable + self->flags &= ~kReceiveOnce; // Disable - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { [self doReceive]; }}); @@ -4161,13 +4398,13 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ - flags &= ~kReceiveOnce; // Disable - flags &= ~kReceiveContinuous; // Disable + self->flags &= ~kReceiveOnce; // Disable + self->flags &= ~kReceiveContinuous; // Disable - if (socket4FDBytesAvailable > 0) { + if (self->socket4FDBytesAvailable > 0) { [self suspendReceive4Source]; } - if (socket6FDBytesAvailable > 0) { + if (self->socket6FDBytesAvailable > 0) { [self suspendReceive6Source]; } }; @@ -4196,20 +4433,20 @@ enum GCDAsyncUdpSocketConfig newFilterBlock = [filterBlock copy]; newFilterQueue = filterQueue; - #if NEEDS_DISPATCH_RETAIN_RELEASE + #if !OS_OBJECT_USE_OBJC dispatch_retain(newFilterQueue); #endif } dispatch_block_t block = ^{ - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (receiveFilterQueue) dispatch_release(receiveFilterQueue); + #if !OS_OBJECT_USE_OBJC + if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); #endif - receiveFilterBlock = newFilterBlock; - receiveFilterQueue = newFilterQueue; - receiveFilterAsync = isAsynchronous; + self->receiveFilterBlock = newFilterBlock; + self->receiveFilterQueue = newFilterQueue; + self->receiveFilterAsync = isAsynchronous; }; if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) @@ -4315,7 +4552,9 @@ enum GCDAsyncUdpSocketConfig struct sockaddr_in sockaddr4; socklen_t sockaddr4len = sizeof(sockaddr4); - size_t bufSize = MIN(max4ReceiveSize, socket4FDBytesAvailable); + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max4ReceiveSize; void *buf = malloc(bufSize); result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); @@ -4350,7 +4589,9 @@ enum GCDAsyncUdpSocketConfig struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); - size_t bufSize = MIN(max6ReceiveSize, socket6FDBytesAvailable); + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max6ReceiveSize; void *buf = malloc(bufSize); result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); @@ -4422,12 +4663,12 @@ enum GCDAsyncUdpSocketConfig pendingFilterOperations++; dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { - allowed = receiveFilterBlock(data, addr, &filterContext); + allowed = self->receiveFilterBlock(data, addr, &filterContext); // Transition back to socketQueue to get the current delegate / delegateQueue - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - pendingFilterOperations--; + self->pendingFilterOperations--; if (allowed) { @@ -4438,15 +4679,15 @@ enum GCDAsyncUdpSocketConfig LogVerbose(@"received packet silently dropped by receiveFilter"); } - if (flags & kReceiveOnce) + if (self->flags & kReceiveOnce) { if (allowed) { // The delegate has been notified, // so our receive once operation has completed. - flags &= ~kReceiveOnce; + self->flags &= ~kReceiveOnce; } - else if (pendingFilterOperations == 0) + else if (self->pendingFilterOperations == 0) { // All pending filter operations have completed, // and none were allowed through. @@ -4461,7 +4702,7 @@ enum GCDAsyncUdpSocketConfig { dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { - allowed = receiveFilterBlock(data, addr, &filterContext); + allowed = self->receiveFilterBlock(data, addr, &filterContext); }}); if (allowed) @@ -4588,9 +4829,9 @@ enum GCDAsyncUdpSocketConfig dispatch_block_t block = ^{ @autoreleasepool { - flags |= kCloseAfterSends; + self->flags |= kCloseAfterSends; - if (currentSend == nil && [sendQueue count] == 0) + if (self->currentSend == nil && [self->sendQueue count] == 0) { [self closeWithError:nil]; } @@ -4619,13 +4860,13 @@ static NSThread *listenerThread; dispatch_once(&predicate, ^{ listenerThread = [[NSThread alloc] initWithTarget:self - selector:@selector(listenerThread) + selector:@selector(listenerThread:) object:nil]; [listenerThread start]; }); } -+ (void)listenerThread ++ (void)listenerThread:(id)unused { @autoreleasepool { @@ -5066,6 +5307,7 @@ Failed: #endif +#if TARGET_OS_IPHONE - (void)applicationWillEnterForeground:(NSNotification *)notification { LogTrace(); @@ -5084,6 +5326,7 @@ Failed: else dispatch_async(socketQueue, block); } +#endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Advanced @@ -5116,7 +5359,7 @@ Failed: - (int)socketFD { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5131,7 +5374,7 @@ Failed: - (int)socket4FD { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5143,7 +5386,7 @@ Failed: - (int)socket6FD { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5157,7 +5400,7 @@ Failed: - (CFReadStreamRef)readStream { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5181,7 +5424,7 @@ Failed: - (CFWriteStreamRef)writeStream { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5203,7 +5446,7 @@ Failed: - (BOOL)enableBackgroundingOnSockets { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", THIS_FILE, THIS_METHOD); @@ -5355,7 +5598,7 @@ Failed: { if ([address length] >= sizeof(struct sockaddr_in)) { - const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addrX; + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; @@ -5368,7 +5611,7 @@ Failed: { if ([address length] >= sizeof(struct sockaddr_in6)) { - const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addrX; + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; if (portPtr) *portPtr = [self portFromSockaddr6:addr6];