From 99220d85f8106f02d94e726171d411ff88552875 Mon Sep 17 00:00:00 2001 From: Jake Marsh Date: Thu, 3 Jan 2013 23:15:53 -0600 Subject: [PATCH] Added a black and white menu bar icon and preference to use it, defaulting to current, color icon ala Dropbox.app. Also cleaned up the .gitignore to ignore CocoaPods stuff, as like rubygems, it's best to keep these files out of source control. --- .gitignore | 2 + Podfile.lock | 11 - .../CocoaAsyncSocket/AsyncSocket.h | 1 - .../CocoaAsyncSocket/AsyncUdpSocket.h | 1 - .../CocoaAsyncSocket/GCDAsyncSocket.h | 1 - .../CocoaAsyncSocket/GCDAsyncUdpSocket.h | 1 - .../CocoaAsyncSocket/CocoaAsyncSocket.podspec | 26 - Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.h | 1074 --- Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m | 7430 ----------------- Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h | 920 -- Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m | 5348 ------------ Pods/CocoaAsyncSocket/README.markdown | 70 - Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.h | 659 -- Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.m | 4312 ---------- .../CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h | 370 - .../CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m | 2297 ----- Pods/Headers/CocoaAsyncSocket/AsyncSocket.h | 1 - .../Headers/CocoaAsyncSocket/AsyncUdpSocket.h | 1 - .../Headers/CocoaAsyncSocket/GCDAsyncSocket.h | 1 - .../CocoaAsyncSocket/GCDAsyncUdpSocket.h | 1 - Pods/Pods-Acknowledgements.markdown | 3 - Pods/Pods-Acknowledgements.plist | 29 - Pods/Pods-prefix.pch | 3 - Pods/Pods-resources.sh | 23 - Pods/Pods.xcconfig | 7 - Pods/Pods.xcodeproj/project.pbxproj | 711 -- Pods/PodsDummy_Pods.m | 4 - QuickHue.xcodeproj/project.pbxproj | 16 + QuickHue/DPQuickHueAppDelegate.m | 24 +- QuickHue/DPQuickHuePrefsViewController.h | 5 +- QuickHue/DPQuickHuePrefsViewController.m | 19 + QuickHue/DPQuickHuePrefsViewController.xib | 1100 +-- QuickHue/Images/bulb-black.png | Bin 0 -> 1298 bytes QuickHue/Images/bulb-black@2x.png | Bin 0 -> 1292 bytes QuickHue/Images/bulb-white.png | Bin 0 -> 1303 bytes QuickHue/Images/bulb-white@2x.png | Bin 0 -> 1302 bytes 36 files changed, 683 insertions(+), 23788 deletions(-) delete mode 100644 Podfile.lock delete mode 120000 Pods/BuildHeaders/CocoaAsyncSocket/AsyncSocket.h delete mode 120000 Pods/BuildHeaders/CocoaAsyncSocket/AsyncUdpSocket.h delete mode 120000 Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncSocket.h delete mode 120000 Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncUdpSocket.h delete mode 100644 Pods/CocoaAsyncSocket/CocoaAsyncSocket.podspec delete mode 100644 Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.h delete mode 100644 Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m delete mode 100644 Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h delete mode 100644 Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m delete mode 100644 Pods/CocoaAsyncSocket/README.markdown delete mode 100644 Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.h delete mode 100644 Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.m delete mode 100644 Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h delete mode 100644 Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m delete mode 120000 Pods/Headers/CocoaAsyncSocket/AsyncSocket.h delete mode 120000 Pods/Headers/CocoaAsyncSocket/AsyncUdpSocket.h delete mode 120000 Pods/Headers/CocoaAsyncSocket/GCDAsyncSocket.h delete mode 120000 Pods/Headers/CocoaAsyncSocket/GCDAsyncUdpSocket.h delete mode 100644 Pods/Pods-Acknowledgements.markdown delete mode 100644 Pods/Pods-Acknowledgements.plist delete mode 100644 Pods/Pods-prefix.pch delete mode 100755 Pods/Pods-resources.sh delete mode 100644 Pods/Pods.xcconfig delete mode 100644 Pods/Pods.xcodeproj/project.pbxproj delete mode 100644 Pods/PodsDummy_Pods.m create mode 100644 QuickHue/Images/bulb-black.png create mode 100644 QuickHue/Images/bulb-black@2x.png create mode 100644 QuickHue/Images/bulb-white.png create mode 100644 QuickHue/Images/bulb-white@2x.png diff --git a/.gitignore b/.gitignore index 89c499e..9f60bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ profile *.moved-aside DerivedData .idea/ +Podfile.lock +Pods/* \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index f001e77..0000000 --- a/Podfile.lock +++ /dev/null @@ -1,11 +0,0 @@ - -DEPENDENCIES: -- CocoaAsyncSocket (~> 0.0.1) - -COCOAPODS: 0.16.0 - -PODS: -- CocoaAsyncSocket (0.0.1) - -SPEC CHECKSUMS: - CocoaAsyncSocket: 70f5de3c1ba13526c23df75fc98a0ea3049aa19c diff --git a/Pods/BuildHeaders/CocoaAsyncSocket/AsyncSocket.h b/Pods/BuildHeaders/CocoaAsyncSocket/AsyncSocket.h deleted file mode 120000 index afce6cb..0000000 --- a/Pods/BuildHeaders/CocoaAsyncSocket/AsyncSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/RunLoop/AsyncSocket.h \ No newline at end of file diff --git a/Pods/BuildHeaders/CocoaAsyncSocket/AsyncUdpSocket.h b/Pods/BuildHeaders/CocoaAsyncSocket/AsyncUdpSocket.h deleted file mode 120000 index 0d51519..0000000 --- a/Pods/BuildHeaders/CocoaAsyncSocket/AsyncUdpSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h \ No newline at end of file diff --git a/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncSocket.h b/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncSocket.h deleted file mode 120000 index 696efcd..0000000 --- a/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/GCD/GCDAsyncSocket.h \ No newline at end of file diff --git a/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncUdpSocket.h b/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncUdpSocket.h deleted file mode 120000 index eef8ea2..0000000 --- a/Pods/BuildHeaders/CocoaAsyncSocket/GCDAsyncUdpSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h \ No newline at end of file diff --git a/Pods/CocoaAsyncSocket/CocoaAsyncSocket.podspec b/Pods/CocoaAsyncSocket/CocoaAsyncSocket.podspec deleted file mode 100644 index 568d6f7..0000000 --- a/Pods/CocoaAsyncSocket/CocoaAsyncSocket.podspec +++ /dev/null @@ -1,26 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'CocoaAsyncSocket' - s.version = '0.0.1' - s.license = 'public domain' - s.summary = 'An asynchronous socket networking library for Cocoa.' - s.homepage = 'http://code.google.com/p/cocoaasyncsocket/' - s.authors = 'Dustin Voss', { 'Robbie Hanson' => 'robbiehanson@deusty.com' } - - s.source = { :git => 'https://github.com/robbiehanson/CocoaAsyncSocket.git', :commit => 'a87a901f6b3bbc83e2c449ffd33515f8d31da2f8' } - - s.description = 'CocoaAsyncSocket supports TCP and UDP. The AsyncSocket class is for TCP, and the AsyncUdpSocket class is for UDP. ' \ - 'AsyncSocket is a TCP/IP socket networking library that wraps CFSocket and CFStream. It offers asynchronous ' \ - 'operation, and a native Cocoa class complete with delegate support or use the GCD variant GCDAsyncSocket. ' \ - 'AsyncUdpSocket is a UDP/IP socket networking library that wraps CFSocket. It works almost exactly like the TCP ' \ - 'version, but is designed specifically for UDP. This includes queued non-blocking send/receive operations, full ' \ - 'delegate support, run-loop based, self-contained class, and support for IPv4 and IPv6.' - - s.source_files = '{GCD,RunLoop}/*.{h,m}' - s.clean_paths = 'Vendor', 'GCD/Xcode', 'RunLoop/Xcode' - s.requires_arc = true - if config.ios? - s.frameworks = ['CFNetwork', 'Security'] - else - s.frameworks = ['CoreServices', 'Security'] - end -end \ No newline at end of file diff --git a/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.h b/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.h deleted file mode 100644 index cf9927f..0000000 --- a/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.h +++ /dev/null @@ -1,1074 +0,0 @@ -// -// GCDAsyncSocket.h -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q3 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import -#import -#import - -@class GCDAsyncReadPacket; -@class GCDAsyncWritePacket; -@class GCDAsyncSocketPreBuffer; - -#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 - -extern NSString *const GCDAsyncSocketException; -extern NSString *const GCDAsyncSocketErrorDomain; - -extern NSString *const GCDAsyncSocketQueueName; -extern NSString *const GCDAsyncSocketThreadName; - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -extern NSString *const GCDAsyncSocketSSLCipherSuites; -#if TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -#else -extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; -#endif -#endif - -enum GCDAsyncSocketError -{ - GCDAsyncSocketNoError = 0, // Never used - GCDAsyncSocketBadConfigError, // Invalid configuration - GCDAsyncSocketBadParamError, // Invalid parameter was passed - GCDAsyncSocketConnectTimeoutError, // A connect operation timed out - GCDAsyncSocketReadTimeoutError, // A read operation timed out - GCDAsyncSocketWriteTimeoutError, // A write operation timed out - GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing - GCDAsyncSocketClosedError, // The remote peer closed the connection - GCDAsyncSocketOtherError, // Description provided in userInfo -}; -typedef enum GCDAsyncSocketError GCDAsyncSocketError; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncSocket : NSObject - -/** - * GCDAsyncSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. - * If you choose to provide a socket queue, and the socket queue has a configured target queue, - * then please see the discussion for the method markSocketQueueTargetQueue. - * - * 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; - -#pragma mark Configuration - -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; - -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(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; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols, - * and can simulataneously accept incoming connections on either protocol. - * - * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol. - * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6. - * 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; - -- (BOOL)isIPv4PreferredOverIPv6; -- (void)setPreferIPv4OverIPv6:(BOOL)flag; - -/** - * 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; - -#pragma mark Accepting - -/** - * Tells the socket to begin listening and accepting connections on the given port. - * 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)acceptOnPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * This method is the same as acceptOnPort:error: with the - * additional option of specifying which interface to listen on. - * - * For example, you could specify that the socket should only accept connections over ethernet, - * and not other interfaces such as wifi. - * - * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - * - * You can see the list of interfaces via the command line utility "ifconfig", - * or programmatically via the getifaddrs() function. - * - * 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; - -#pragma mark Connecting - -/** - * Connects to the given host and port. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: - * and uses the default interface, and no timeout. -**/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects to the given host and port with an optional timeout. - * - * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given host & port, via the optional interface, with an optional timeout. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * The host may also be the special strings "localhost" or "loopback" to specify connecting - * to a service on the local machine. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * 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 -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * This method is the same as connectToAddress:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToAddress:error: method. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -/** - * Connects to the given address, using the specified interface and timeout. - * - * The address is specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * The interface may also be used to specify the local port (see below). - * - * The timeout is optional. To not time out use a negative time interval. - * - * This method will return NO if an error is detected, and set the error pointer (if one was given). - * Possible errors would be a nil host, invalid interface, or socket is already connected. - * - * If no errors are detected, this method will start a background connect operation and immediately return YES. - * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable. - * - * Since this class supports queued reads and writes, you can immediately start reading and/or writing. - * All read/write operations will be queued, and upon socket connection, - * the operations will be dequeued and processed in order. - * - * The interface may optionally contain a port number at the end of the string, separated by a colon. - * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end) - * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424". - * To specify only local port: ":8082". - * Please note this is an advanced feature, and is somewhat hidden on purpose. - * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection. - * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere. - * Local ports do NOT need to match remote ports. In fact, they almost never do. - * This feature is here for networking professionals using very advanced techniques. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterface:(NSString *)interface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -#pragma mark Disconnecting - -/** - * Disconnects immediately (synchronously). Any pending reads or writes are dropped. - * - * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method - * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods). - * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns. - * - * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method) - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket release]; - * - * If you plan on disconnecting the socket, and then immediately asking it to connect again, - * you'll likely want to do so like this: - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket setDelegate:self]; - * [asyncSocket connect...]; -**/ -- (void)disconnect; - -/** - * Disconnects after all pending reads have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending writes. -**/ -- (void)disconnectAfterReading; - -/** - * Disconnects after all pending writes have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending reads. -**/ -- (void)disconnectAfterWriting; - -/** - * Disconnects after all pending reads and writes have completed. - * After calling this, the read and write methods will do nothing. -**/ -- (void)disconnectAfterReadingAndWriting; - -#pragma mark Diagnostics - -/** - * Returns whether the socket is disconnected or connected. - * - * A disconnected socket may be recycled. - * That is, it can 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; - -/** - * 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; - -- (NSString *)localHost; -- (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. -**/ -- (NSData *)connectedAddress; -- (NSData *)localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. -**/ -- (BOOL)isIPv4; -- (BOOL)isIPv6; - -/** - * Returns whether or not the socket has been secured via SSL/TLS. - * - * See also the startTLS method. -**/ -- (BOOL)isSecure; - -#pragma mark Reading - -// The readData and writeData methods won't block (they are asynchronous). -// -// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue. -// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue. -// -// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) -// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method -// is called to optionally allow you to extend the timeout. -// Upon a timeout, the "socket:didDisconnectWithError:" method is called -// -// The tag is for your convenience. -// You can use it as an array index, step number, state id, pointer, etc. - -/** - * Reads the first available bytes that become available on the socket. - * - * If the timeout value is negative, the read operation will not use a timeout. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * 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 maxLength is zero, no length restriction is enforced. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Reads the given number of bytes. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If the length is 0, this method does nothing and the delegate is not called. -**/ -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the given number of bytes. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 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, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. -**/ -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * 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; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 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. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * 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 - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * 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 maxLength:(NSUInteger)length tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass a maxLength parameter that is less than the length of the data (separator) parameter, - * the method will do nothing (except maybe print a warning), and the delegate will not be called. - * 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. - * - * If you pass a buffer, you must not alter it in any way while the socket is using it. - * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer via - * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO]. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * If you're developing your own custom protocol, be sure your separator can not occur naturally as - * part of the data between separators. - * For example, imagine you want to send several small documents over a socket. - * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents. - * In this particular example, it would be better to use a protocol similar to HTTP with - * a header that includes the length of the document. - * Also be careful that your separator cannot occur naturally as part of the encoding for a character. - * - * The given data (separator) parameter should be immutable. - * 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 - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * 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; - -#pragma mark Writing - -/** - * Writes data to the socket, and calls the delegate when finished. - * - * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. - * If the timeout value is negative, the write operation will not use a timeout. - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method - * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed. - * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it. - * This is for performance reasons. Often times, if NSMutableData is passed, it is because - * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * 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; - -/** - * 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; - -#pragma mark Security - -/** - * Secures the connection using SSL/TLS. - * - * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes - * 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 possible keys and values for the TLS settings are well documented. - * Standard keys are: - * - * - kCFStreamSSLLevel - * - kCFStreamSSLAllowsExpiredCertificates - * - kCFStreamSSLAllowsExpiredRoots - * - kCFStreamSSLAllowsAnyRoot - * - kCFStreamSSLValidatesCertificateChain - * - kCFStreamSSLPeerName - * - kCFStreamSSLCertificates - * - kCFStreamSSLIsServer - * - * If SecureTransport is available on iOS: - * - * - GCDAsyncSocketSSLCipherSuites - * - GCDAsyncSocketSSLProtocolVersionMin - * - GCDAsyncSocketSSLProtocolVersionMax - * - * If SecureTransport is available on Mac OS X: - * - * - GCDAsyncSocketSSLCipherSuites - * - GCDAsyncSocketSSLDiffieHellmanParameters; - * - * - * Please refer to Apple's documentation for associated values, as well as other possible keys. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * 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 - * give it a name to verify against via the kCFStreamSSLPeerName key. - * The security implications of this are important to understand. - * Imagine you are attempting to create a secure connection to MySecureServer.com, - * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. - * If you simply use the default settings, and MaliciousServer.com has a valid certificate, - * 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; - -#pragma mark Advanced - -/** - * Traditionally sockets are not closed until the conversation is over. - * However, it is technically possible for the remote enpoint to close its write stream. - * Our socket would then be notified that there is no more data to be read, - * but our socket would still be writeable and the remote endpoint could continue to receive our data. - * - * The argument for this confusing functionality stems from the idea that a client could shut down its - * write stream after sending a request to the server, thus notifying the server there are to be no further requests. - * In practice, however, this technique did little to help server developers. - * - * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close - * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell - * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work. - * Otherwise an error will be occur shortly (when the remote end sends us a RST packet). - * - * In addition to the technical challenges and confusion, many high level socket/stream API's provide - * no support for dealing with the problem. If the read stream is closed, the API immediately declares the - * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does. - * It might sound like poor design at first, but in fact it simplifies development. - * - * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket. - * Thus it actually makes sense to close the socket at this point. - * And in fact this is what most networking developers want and expect to happen. - * However, if you are writing a server that interacts with a plethora of clients, - * you might encounter a client that uses the discouraged technique of shutting down its write stream. - * If this is the case, you can set this property to NO, - * and make use of the socketDidCloseReadStream delegate method. - * - * The default value is YES. -**/ -- (BOOL)autoDisconnectOnClosedReadStream; -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag; - -/** - * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. - * In most cases, the instance creates this queue itself. - * However, to allow for maximum flexibility, the internal queue may be passed in the init method. - * This allows for some advanced options such as controlling socket priority via target queues. - * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. - * - * For example, imagine there are 2 queues: - * dispatch_queue_t socketQueue; - * dispatch_queue_t socketTargetQueue; - * - * If you do this (pseudo-code): - * socketQueue.targetQueue = socketTargetQueue; - * - * Then all socketQueue operations will actually get run on the given socketTargetQueue. - * This is fine and works great in most situations. - * But if you run code directly from within the socketTargetQueue that accesses the socket, - * you could potentially get deadlock. Imagine the following code: - * - * - (BOOL)socketHasSomething - * { - * __block BOOL result = NO; - * dispatch_block_t block = ^{ - * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; - * } - * if (is_executing_on_queue(socketQueue)) - * block(); - * else - * dispatch_sync(socketQueue, block); - * - * return result; - * } - * - * What happens if you call this method from the socketTargetQueue? The result is deadlock. - * This is because the GCD API offers no mechanism to discover a queue's targetQueue. - * Thus we have no idea if our socketQueue is configured with a targetQueue. - * If we had this information, we could easily avoid deadlock. - * But, since these API's are missing or unfeasible, you'll have to explicitly set it. - * - * IF you pass a socketQueue via the init method, - * AND you've configured the passed socketQueue with a targetQueue, - * THEN you should pass the end queue in the target hierarchy. - * - * For example, consider the following queue hierarchy: - * socketQueue -> ipQueue -> moduleQueue - * - * This example demonstrates priority shaping within some server. - * All incoming client connections from the same IP address are executed on the same target queue. - * And all connections for a particular module are executed on the same target queue. - * Thus, the priority of all networking for the entire module can be changed on the fly. - * Additionally, networking traffic from a single IP cannot monopolize the module. - * - * Here's how you would accomplish something like that: - * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock - * { - * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); - * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; - * - * dispatch_set_target_queue(socketQueue, ipQueue); - * dispatch_set_target_queue(iqQueue, moduleQueue); - * - * return socketQueue; - * } - * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket - * { - * [clientConnections addObject:newSocket]; - * [newSocket markSocketQueueTargetQueue:moduleQueue]; - * } - * - * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. - * This is often NOT the case, as such queues are used solely for execution shaping. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket is a server socket (is accepting incoming connections), - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's internal CFReadStream/CFWriteStream. - * - * These streams are only used as workarounds for specific iOS shortcomings: - * - * - Apple has decided to keep the SecureTransport framework private is iOS. - * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it. - * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream, - * instead of the preferred and faster and more powerful SecureTransport. - * - * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded, - * Apple only bothers to notify us via the CFStream API. - * The faster and more powerful GCD API isn't notified properly in this case. - * - * See also: (BOOL)enableBackgroundingOnSocket -**/ -- (CFReadStreamRef)readStream; -- (CFWriteStreamRef)writeStream; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Note: Apple does not officially support backgrounding server sockets. - * That is, if your socket is accepting incoming connections, Apple does not officially support - * allowing iOS applications to accept incoming connections while an app is backgrounded. - * - * Example usage: - * - * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port - * { - * [asyncSocket performBlock:^{ - * [asyncSocket enableBackgroundingOnSocket]; - * }]; - * } -**/ -- (BOOL)enableBackgroundingOnSocket; - -#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 - -#pragma mark Utilities - -/** - * Extracting host and port information from raw address data. -**/ -+ (NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(NSData *)address; -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address; - -/** - * A few common line separators, for use with the readDataToData:... methods. -**/ -+ (NSData *)CRLFData; // 0x0D0A -+ (NSData *)CRData; // 0x0D -+ (NSData *)LFData; // 0x0A -+ (NSData *)ZeroData; // 0x00 - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol GCDAsyncSocketDelegate -@optional - -/** - * This method is called immediately prior to socket:didAcceptNewSocket:. - * It optionally allows a listening socket to specify the socketQueue for a new accepted socket. - * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue. - * - * Since you cannot autorelease a dispatch_queue, - * this method uses the "new" prefix in its name to specify that the returned queue has been retained. - * - * Thus you could do something like this in the implementation: - * return dispatch_queue_create("MyQueue", NULL); - * - * If you are placing multiple sockets on the same queue, - * then care should be taken to increment the retain count each time this method is invoked. - * - * For example, your implementation might look something like this: - * dispatch_retain(myExistingQueue); - * return myExistingQueue; -**/ -- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock; - -/** - * Called when a socket accepts a connection. - * Another socket is automatically spawned to handle it. - * - * You must retain the newSocket if you wish to handle the connection. - * Otherwise the newSocket instance will be released and the spawned connection will be closed. - * - * By default the new socket will have the same delegate and delegateQueue. - * You may, of course, change this at any time. -**/ -- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket; - -/** - * 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 didConnectToHost:(NSString *)host port:(uint16_t)port; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; - -/** - * 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. -**/ -- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called when a socket has completed writing the requested data. Not called if there is an error. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; - -/** - * 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. -**/ -- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called if a read operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been read so far for the read operation. - * - * Note that this method may be called multiple times for a single read if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called if a write operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been written so far for the write operation. - * - * Note that this method may be called multiple times for a single write if you return positive numbers. -**/ -- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Conditionally called if the read stream closes, but the write stream may still be writeable. - * - * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO. - * See the discussion on the autoDisconnectOnClosedReadStream method for more information. -**/ -- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock; - -/** - * 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. -**/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err; - -/** - * Called after the socket has successfully completed SSL/TLS negotiation. - * This method is not called unless you use the provided startTLS method. - * - * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, - * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code. -**/ -- (void)socketDidSecure:(GCDAsyncSocket *)sock; - -@end diff --git a/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m b/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m deleted file mode 100644 index 5722ac3..0000000 --- a/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m +++ /dev/null @@ -1,7430 +0,0 @@ -// -// GCDAsyncSocket.m -// -// This class is in the public domain. -// Originally created by Robbie Hanson in Q4 2010. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import "GCDAsyncSocket.h" - -#if TARGET_OS_IPHONE -#import -#endif - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// 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 - -#endif - - -#if 0 - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// https://github.com/robbiehanson/CocoaLumberjack -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync YES -#define LogContext 65535 - -#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__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -// Log levels : off, error, warn, info, verbose -static const int logLevel = LOG_LEVEL_VERBOSE; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) {} -#define LogWarn(frmt, ...) {} -#define LogInfo(frmt, ...) {} -#define LogVerbose(frmt, ...) {} - -#define LogCError(frmt, ...) {} -#define LogCWarn(frmt, ...) {} -#define LogCInfo(frmt, ...) {} -#define LogCVerbose(frmt, ...) {} - -#define LogTrace() {} -#define LogCTrace(frmt, ...) {} - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. -**/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. -**/ -#define SOCKET_NULL -1 - - -NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException"; -NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain"; - -NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket"; -NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream"; - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; -#if TARGET_OS_IPHONE -NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; -NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -#else -NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; -#endif -#endif - -enum GCDAsyncSocketFlags -{ - kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) - kConnected = 1 << 1, // If set, the socket is connected - kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed - kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout - kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout - kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued - kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. - kReadSourceSuspended = 1 << 8, // If set, the read source is suspended - kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended - kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS - kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete - 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 -#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 -#endif -}; - -enum GCDAsyncSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 - kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes -}; - -#if TARGET_OS_IPHONE - static NSThread *cfstreamThread; // Used for CFStreams -#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 - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * A PreBuffer is used when there is more data available on the socket - * than is being requested by current read request. - * In this case we slurp up all data from the socket (to minimize sys calls), - * and store additional yet unread data in a "prebuffer". - * - * The prebuffer is entirely drained before we read from the socket again. - * In other words, a large chunk of data is written is written to the prebuffer. - * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)). - * - * A ring buffer was once used for this purpose. - * But a ring buffer takes up twice as much memory as needed (double the size for mirroring). - * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size. - * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. - * - * The current design is very simple and straight-forward, while also keeping memory requirements lower. -**/ - -@interface GCDAsyncSocketPreBuffer : NSObject -{ - uint8_t *preBuffer; - size_t preBufferSize; - - uint8_t *readPointer; - uint8_t *writePointer; -} - -- (id)initWithCapacity:(size_t)numBytes; - -- (void)ensureCapacityForWrite:(size_t)numBytes; - -- (size_t)availableBytes; -- (uint8_t *)readBuffer; - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr; - -- (size_t)availableSpace; -- (uint8_t *)writeBuffer; - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr; - -- (void)didRead:(size_t)bytesRead; -- (void)didWrite:(size_t)bytesWritten; - -- (void)reset; - -@end - -@implementation GCDAsyncSocketPreBuffer - -- (id)initWithCapacity:(size_t)numBytes -{ - if ((self = [super init])) - { - preBufferSize = numBytes; - preBuffer = malloc(preBufferSize); - - readPointer = preBuffer; - writePointer = preBuffer; - } - return self; -} - -- (void)dealloc -{ - if (preBuffer) - free(preBuffer); -} - -- (void)ensureCapacityForWrite:(size_t)numBytes -{ - size_t availableSpace = preBufferSize - (writePointer - readPointer); - - if (numBytes > availableSpace) - { - size_t additionalBytes = numBytes - availableSpace; - - size_t newPreBufferSize = preBufferSize + additionalBytes; - uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); - - size_t readPointerOffset = readPointer - preBuffer; - size_t writePointerOffset = writePointer - preBuffer; - - preBuffer = newPreBuffer; - preBufferSize = newPreBufferSize; - - readPointer = preBuffer + readPointerOffset; - writePointer = preBuffer + writePointerOffset; - } -} - -- (size_t)availableBytes -{ - return writePointer - readPointer; -} - -- (uint8_t *)readBuffer -{ - return readPointer; -} - -- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr -{ - if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = writePointer - readPointer; -} - -- (void)didRead:(size_t)bytesRead -{ - readPointer += bytesRead; - - if (readPointer == writePointer) - { - // The prebuffer has been drained. Reset pointers. - readPointer = preBuffer; - writePointer = preBuffer; - } -} - -- (size_t)availableSpace -{ - return preBufferSize - (writePointer - readPointer); -} - -- (uint8_t *)writeBuffer -{ - return writePointer; -} - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr -{ - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer); -} - -- (void)didWrite:(size_t)bytesWritten -{ - writePointer += bytesWritten; -} - -- (void)reset -{ - readPointer = preBuffer; - writePointer = preBuffer; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncReadPacket encompasses the instructions for any given read. - * The content of a read packet allows the code to determine if we're: - * - reading to a certain length - * - reading to a certain separator - * - or simply reading the first chunk of available data -**/ -@interface GCDAsyncReadPacket : NSObject -{ - @public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - 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; - -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead; - -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr; - -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable; -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr; -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr; - -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes; - -@end - -@implementation GCDAsyncReadPacket - -- (id)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])) - { - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - } - return self; -} - -/** - * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. -**/ -- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead -{ - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; - - [buffer increaseLengthBy:buffInc]; - } -} - -/** - * This method is used when we do NOT know how much data is available to be read from the socket. - * This method returns the default value unless it exceeds the specified readLength or maxLength. - * - * Furthermore, the shouldPreBuffer decision is based upon the packet type, - * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. -**/ -- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSUInteger result; - - if (readLength > 0) - { - // Read a specific length of data - - result = MIN(defaultValue, (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, - // it would have to be resized eventually anyway. - - if (shouldPreBufferPtr) - *shouldPreBufferPtr = NO; - } - else - { - // Either reading until we find a specified terminator, - // or we're simply reading all available data. - // - // In other words, one of: - // - // - readDataToData packet - // - readDataWithTimeout packet - - if (maxLength > 0) - result = MIN(defaultValue, (maxLength - bytesDone)); - else - result = defaultValue; - - // Since we don't know the size of the read in advance, - // the shouldPreBuffer decision is based upon whether the returned value would fit - // in the current buffer without requiring a resize of the buffer. - // - // This is because, in all likelyhood, the amount read from the socket will be less than the default value. - // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (buffSpace >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - } - - return result; -} - -/** - * For read packets without a set terminator, returns the amount of data - * that can be read without exceeding the readLength or maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * The given hint MUST be greater than zero. -**/ -- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable -{ - NSAssert(term == nil, @"This method does not apply to term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - if (readLength > 0) - { - // Read a specific length of data - - return MIN(bytesAvailable, (readLength - bytesDone)); - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read a certain length of data that exceeds the size of the buffer, - // then it is clear that our code will resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - } - else - { - // Read all available data - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read all available data without giving us a maxLength, - // then it is clear that our code might resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - - return result; - } -} - -/** - * For read packets with a set terminator, returns the amount of data - * that can be read without exceeding the maxLength. - * - * The given parameter indicates the number of bytes estimated to be available on the socket, - * which is taken into consideration during the calculation. - * - * To optimize memory allocations, mem copies, and mem moves - * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, - * or if the data can be read directly into the read packet's buffer. -**/ -- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // Should the data be read into the read packet's buffer, or into a pre-buffer first? - // - // One would imagine the preferred option is the faster one. - // So which one is faster? - // - // Reading directly into the packet's buffer requires: - // 1. Possibly resizing packet buffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) - // - // Reading into prebuffer first: - // 1. Possibly resizing prebuffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) - // 5. Removing underflow from prebuffer (memmove) - // - // Comparing the performance of the two we can see that reading - // data into the prebuffer first is slower due to the extra memove. - // - // However: - // The implementation of NSMutableData is open source via core foundation's CFMutableData. - // Decreasing the length of a mutable data object doesn't cause a realloc. - // In other words, the capacity of a mutable data object can grow, but doesn't shrink. - // - // This means the prebuffer will rarely need a realloc. - // The packet buffer, on the other hand, may often need a realloc. - // This is especially true if we are the buffer owner. - // Furthermore, if we are constantly realloc'ing the packet buffer, - // and then moving the overflow into the prebuffer, - // then we're consistently over-allocating memory for each term read. - // And now we get into a bit of a tradeoff between speed and memory utilization. - // - // The end result is that the two perform very similarly. - // And we can answer the original question very simply by another means. - // - // If we can read all the data directly into the packet's buffer without resizing it first, - // then we do so. Otherwise we use the prebuffer. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - if ((buffSize - buffUsed) >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - - return result; -} - -/** - * For read packets with a set terminator, - * returns the amount of data that can be read from the given preBuffer, - * without going over a terminator or the maxLength. - * - * It is assumed the terminator has not already been read. -**/ -- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a _portion_ of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer availableBytes]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } - - uint8_t seq[termLength]; - const void *termBuf = [term bytes]; - - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; - - NSUInteger preLen = termLength - bufLen; - const uint8_t *pre = [preBuffer readBuffer]; - - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - - NSUInteger result = maxPreBufferLength; - - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer - - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); - - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } - - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer - - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic - - result = preOffset + termLength; - found = YES; - break; - } - - pre++; - } - } - - // There is no need to avoid resizing the buffer in this particular situation. - - if (foundPtr) *foundPtr = found; - return result; -} - -/** - * For read packets with a set terminator, scans the packet buffer for the term. - * It is assumed the terminator had not been fully read prior to the new bytes. - * - * If the term is found, the number of excess bytes after the term are returned. - * If the term is not found, this method will return -1. - * - * Note: A return value of zero means the term was found at the very end. - * - * Prerequisites: - * The given number of bytes have been added to the end of our buffer. - * Our bytesDone variable has NOT been changed due to the prebuffered bytes. -**/ -- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - // The implementation of this method is very similar to the above method. - // See the above method for a discussion of the algorithm used here. - - uint8_t *buff = [buffer mutableBytes]; - NSUInteger buffLength = bytesDone + numBytes; - - const void *termBuff = [term bytes]; - NSUInteger termLength = [term length]; - - // Note: We are dealing with unsigned integers, - // so make sure the math doesn't go below zero. - - NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; - - while (i + termLength <= buffLength) - { - uint8_t *subBuffer = buff + startOffset + i; - - if (memcmp(subBuffer, termBuff, termLength) == 0) - { - return buffLength - (i + termLength); - } - - i++; - } - - return -1; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncWritePacket encompasses the instructions for any given write. -**/ -@interface GCDAsyncWritePacket : NSObject -{ - @public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; -} -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation GCDAsyncWritePacket - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; // Retain not copy. For performance as documented in header file. - bytesDone = 0; - timeout = t; - tag = i; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. - * This class my be altered to support more than just TLS in the future. -**/ -@interface GCDAsyncSpecialPacket : NSObject -{ - @public - NSDictionary *tlsSettings; -} -- (id)initWithTLSSettings:(NSDictionary *)settings; -@end - -@implementation GCDAsyncSpecialPacket - -- (id)initWithTLSSettings:(NSDictionary *)settings -{ - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncSocket - -- (id)init -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (id)initWithSocketQueue:(dispatch_queue_t)sq -{ - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (id)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 -{ - if((self = [super init])) - { - delegate = aDelegate; - delegateQueue = dq; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (dq) dispatch_retain(dq); - #endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - connectIndex = 0; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(sq); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); - - readQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentRead = nil; - - writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentWrite = nil; - - preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result; - - dispatch_sync(socketQueue, ^{ - result = delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - delegate = newDelegate; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result; - - dispatch_sync(socketQueue, ^{ - result = delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - delegate = newDelegate; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (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 -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv4Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv4Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv4Disabled; - else - config |= kIPv4Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv6Disabled) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kIPv6Disabled) == 0); - }); - - return result; - } -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kIPv6Disabled; - else - config |= kIPv6Disabled; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4PreferredOverIPv6 -{ - // Note: YES means kPreferIPv6 is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kPreferIPv6) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kPreferIPv6) == 0); - }); - - return result; - } -} - -- (void)setPreferIPv4OverIPv6:(BOOL)flag -{ - // Note: YES means kPreferIPv6 is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kPreferIPv6; - else - config |= kPreferIPv6; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = userData; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (userData != arbitraryUserData) - { - userData = arbitraryUserData; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accepting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self acceptOnInterface:nil port:port error:errPtr]; -} - -- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - // Just in-case interface parameter is immutable. - NSString *interface = [inInterface copy]; - - __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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason:reason]; - - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create dispatch block and run on socketQueue - - dispatch_block_t block = ^{ @autoreleasepool { - - if (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 (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; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol 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) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - // Resolve interface from description - - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create sockets, configure, bind, and listen - - if (enableIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - socket4FD = createSocket(AF_INET, interface4); - - if (socket4FD == SOCKET_NULL) - { - return_from_block; - } - } - - if (enableIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - if (enableIPv4 && (port == 0)) - { - // No specific port was specified, so we allowed the OS to pick an available port for us. - // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. - - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; - addr6->sin6_port = htons([self localPort4]); - } - - socket6FD = createSocket(AF_INET6, interface6); - - if (socket6FD == SOCKET_NULL) - { - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - } - - return_from_block; - } - } - - // Create accept sources - - if (enableIPv4) - { - accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); - - int socketFD = socket4FD; - dispatch_source_t acceptSource = accept4Source; - - dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { - - LogVerbose(@"event4Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([self doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(accept4Source, ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(accept4Source); - } - - if (enableIPv6) - { - accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - int socketFD = socket6FD; - dispatch_source_t acceptSource = accept6Source; - - dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { - - LogVerbose(@"event6Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([self doAccept:socketFD] && (++i < numPendingConnections)); - }}); - - dispatch_source_set_cancel_handler(accept6Source, ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket6FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(accept6Source); - } - - 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 childSocketFD; - NSData *childSocketAddress; - - if (parentSocketFD == socket4FD) - { - isIPv4 = YES; - - struct sockaddr_in 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]; - } - else // if (parentSocketFD == socket6FD) - { - isIPv4 = NO; - - struct sockaddr_in6 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 - - int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); - return NO; - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Notify delegate - - if (delegateQueue) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - // Query delegate for custom socket queue - - dispatch_queue_t childSocketQueue = NULL; - - if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) - { - childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress - onSocket:self]; - } - - // Create GCDAsyncSocket instance for accepted socket - - GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate - delegateQueue:delegateQueue - socketQueue:childSocketQueue]; - - if (isIPv4) - acceptedSocket->socket4FD = childSocketFD; - else - acceptedSocket->socket6FD = childSocketFD; - - acceptedSocket->flags = (kSocketStarted | kConnected); - - // Setup read and write sources for accepted socket - - dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool { - - [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; - }}); - - // Notify delegate - - if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) - { - [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; - } - - // Release the socket queue returned from the delegate (it was retained by acceptedSocket) - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (childSocketQueue) dispatch_release(childSocketQueue); - #endif - - // The accepted socket should have been retained by the delegate. - // Otherwise it gets properly released when exiting the block. - }}); - } - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connection attempt. - * It is shared between the connectToHost and connectToAddress methods. - * -**/ -- (BOOL)preConnectWithInterface:(NSString *)interface 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; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (interface) - { - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - - if ((interface4 == nil) && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterface4 = interface4; - connectInterface6 = interface6; - } - - // 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]; -} - -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToHost:(NSString *)inHost - onPort:(uint16_t)port - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSString *host = [inHost copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([host length] == 0) - { - NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - flags |= kSocketStarted; - - LogVerbose(@"Dispatching DNS lookup..."); - - // It's possible that the given host parameter is actually a NSMutableString. - // 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]; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex host:hostCpy port:port]; - }}); - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; -} - -- (BOOL)connectToAddress:(NSData *)inRemoteAddr - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); - - // Just in case immutable objects were passed - NSData *remoteAddr = [inRemoteAddr copy]; - NSString *interface = [inInterface copy]; - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with remoteAddr parameter - - NSData *address4 = nil; - NSData *address6 = nil; - - if ([remoteAddr length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; - - if (sockaddr->sa_family == AF_INET) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - address4 = remoteAddr; - } - } - else if (sockaddr->sa_family == AF_INET6) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - address6 = remoteAddr; - } - } - } - - if ((address4 == nil) && (address6 == nil)) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address4 != nil)) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (address6 != nil)) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - return_from_block; - } - - flags |= kSocketStarted; - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port -{ - 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. - - 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)); - - 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 - 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) - { - 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); - - if ((address4 == nil) && (address6 == nil)) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didFail:error]; - }}); - } - else - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } -} - -- (void)lookup:(int)aConnectIndex 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) - { - LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - // Check for problems - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (address6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - if (isIPv6Disabled && (address4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - // Start the normal connection process - - NSError *err = nil; - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - [self closeWithError:err]; - } -} - -/** - * This method is called if the DNS lookup fails. - * This method is executed on the socketQueue. - * - * Since the DNS lookup executed synchronously on a global concurrent queue, - * 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 -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring lookup:didFail: - already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self endConnectTimeout]; - [self closeWithError:error]; -} - -- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]); - LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]); - - // Determine socket type - - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - - // 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; - } - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"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; - } - } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aConnectIndex = connectIndex; - - 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]); - if (result == 0) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didConnect:aConnectIndex]; - }}); - } - else - { - NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didNotConnect:aConnectIndex error:error]; - }}); - } - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (void)didConnect:(int)aConnectIndex -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring didConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - flags |= kConnected; - - [self endConnectTimeout]; - - #if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the connectIndex. - aConnectIndex = connectIndex; - #endif - - // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) - // - // Note: - // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // - // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. - // This gives the delegate time to properly configure the streams if needed. - - dispatch_block_t SetupStreamsPart1 = ^{ - #if TARGET_OS_IPHONE - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:NO]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - #endif - }; - dispatch_block_t SetupStreamsPart2 = ^{ - #if TARGET_OS_IPHONE - - if (aConnectIndex != connectIndex) - { - // The socket has been disconnected. - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } - - #endif - }; - - // Notify delegate - - NSString *host = [self connectedHost]; - uint16_t port = [self connectedPort]; - - if (delegateQueue && [delegate 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 { - - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } - - // Get the connected socket - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD; - - // Enable non-blocking IO on the socket - - int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; - [self closeWithError:[self otherError:errMsg]]; - - return; - } - - // Setup our read/write sources - - [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; - - // Dequeue any pending read/write requests - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; -} - -- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aConnectIndex != connectIndex) - { - LogInfo(@"Ignoring didNotConnect, already disconnected"); - - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } - - [self closeWithError:error]; -} - -- (void)startConnectTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { - - [self doConnectTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); - - dispatch_resume(connectTimer); - } -} - -- (void)endConnectTimeout -{ - LogTrace(); - - if (connectTimer) - { - dispatch_source_cancel(connectTimer); - connectTimer = NULL; - } - - // Increment connectIndex. - // 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++; - - if (connectInterface4) - { - connectInterface4 = nil; - } - if (connectInterface6) - { - connectInterface6 = nil; - } -} - -- (void)doConnectTimeout -{ - LogTrace(); - - [self endConnectTimeout]; - [self closeWithError:[self connectTimeoutError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - [self endConnectTimeout]; - - if (currentRead != nil) [self endCurrentRead]; - if (currentWrite != nil) [self endCurrentWrite]; - - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - [preBuffer reset]; - - #if TARGET_OS_IPHONE - { - if (readStream || writeStream) - { - [self removeStreamsFromRunLoop]; - - if (readStream) - { - CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - } - } - #endif - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - { - [sslPreBuffer reset]; - sslErrCode = noErr; - - 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; - } - } - #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) - { - LogVerbose(@"manually closing close"); - - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"close(socket6FD)"); - close(socket6FD); - socket6FD = SOCKET_NULL; - } - } - else - { - if (accept4Source) - { - LogVerbose(@"dispatch_source_cancel(accept4Source)"); - dispatch_source_cancel(accept4Source); - - // We never suspend accept4Source - - accept4Source = NULL; - } - - if (accept6Source) - { - LogVerbose(@"dispatch_source_cancel(accept6Source)"); - dispatch_source_cancel(accept6Source); - - // We never suspend accept6Source - - accept6Source = NULL; - } - - if (readSource) - { - LogVerbose(@"dispatch_source_cancel(readSource)"); - dispatch_source_cancel(readSource); - - [self resumeReadSource]; - - readSource = NULL; - } - - if (writeSource) - { - LogVerbose(@"dispatch_source_cancel(writeSource)"); - dispatch_source_cancel(writeSource); - - [self resumeWriteSource]; - - writeSource = NULL; - } - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket4FD = SOCKET_NULL; - socket6FD = 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); - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - - if (shouldCallDelegate) - { - if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:self withError:error]; - }}); - } - } -} - -- (void)disconnect -{ - dispatch_block_t block = ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - [self closeWithError:nil]; - } - }}; - - // Synchronous disconnection, as documented in the header file - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)disconnectAfterReading -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -- (void)disconnectAfterReadingAndWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if (flags & kSocketStarted) - { - flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} - -/** - * Closes the socket if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. -**/ -- (void)maybeClose -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - BOOL shouldClose = NO; - - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - else - { - shouldClose = YES; - } - } - } - else if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - - if (shouldClose) - { - [self closeWithError:nil]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; -} - -- (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errnoErrorWithReason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (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]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; -} - -- (NSError *)connectTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to connect to host timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket maxed out error. -**/ -- (NSError *)readMaxedOutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation reached set maximum length", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)readTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)writeTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Write operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; -} - -- (NSError *)connectionClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Socket closed by remote peer", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)isDisconnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kSocketStarted) ? NO : YES; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kConnected) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSString *)connectedHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)connectedPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - 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]; - }); - - return result; - } -} - -- (NSString *)localHost -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; - } - else - { - __block NSString *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:socket4FD]; - else if (socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:socket6FD]; - }}); - - return result; - } -} - -- (uint16_t)localPort -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; - } - else - { - __block uint16_t result = 0; - - 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]; - }); - - return result; - } -} - -- (NSString *)connectedHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)connectedHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)connectedPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)connectedPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)localHost4 -{ - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - - return nil; -} - -- (NSString *)localHost6 -{ - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; - - return nil; -} - -- (uint16_t)localPort4 -{ - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - - return 0; -} - -- (uint16_t)localPort6 -{ - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; - - return 0; -} - -- (NSString *)connectedHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)connectedHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)connectedPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)connectedPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSString *)localHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; -} - -- (NSString *)localHostFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; -} - -- (uint16_t)localPortFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; -} - -- (uint16_t)localPortFromSocket6:(int)socketFD -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket4FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket4FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isIPv6 -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket6FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; - - dispatch_sync(socketQueue, ^{ - result = (socket6FD != SOCKET_NULL); - }); - - return result; - } -} - -- (BOOL)isSecure -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (flags & kSocketSecure) ? YES : NO; - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = (flags & kSocketSecure) ? YES : NO; - }); - - return result; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Finds the address of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). - * - * The interface description may optionally contain a port number at the end, separated by a colon. - * If a non-zero port parameter is provided, any port number in the interface description is ignored. - * - * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. -**/ -- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr - address6:(NSMutableData **)interfaceAddr6Ptr - fromDescription:(NSString *)interfaceDescription - port:(uint16_t)port -{ - NSMutableData *addr4 = nil; - NSMutableData *addr6 = nil; - - NSString *interface = nil; - - NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; - if ([components count] > 0) - { - NSString *temp = [components objectAtIndex:0]; - if ([temp length] > 0) - { - interface = temp; - } - } - if ([components count] > 1 && port == 0) - { - long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10); - - if (portL > 0 && portL <= UINT16_MAX) - { - port = (uint16_t)portL; - } - } - - if (interface == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interface UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in nativeAddr4; - memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - struct sockaddr_in6 nativeAddr6; - memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD -{ - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { - - LogVerbose(@"readEventBlock"); - - socketFDBytesAvailable = dispatch_source_get_data(readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable); - - if (socketFDBytesAvailable > 0) - [self doReadData]; - else - [self doReadEOF]; - }}); - - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { - - LogVerbose(@"writeEventBlock"); - - flags |= kSocketCanAcceptBytes; - [self doWriteData]; - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; - #endif - - dispatch_source_set_cancel_handler(readSource, ^{ - - LogVerbose(@"readCancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - }); - - dispatch_source_set_cancel_handler(writeSource, ^{ - - LogVerbose(@"writeCancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - }); - - // We will not be able to read until data arrives. - // But we should be able to write immediately. - - socketFDBytesAvailable = 0; - flags &= ~kReadSourceSuspended; - - LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); - - flags |= kSocketCanAcceptBytes; - flags |= kWriteSourceSuspended; -} - -- (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; - } - } - #endif - - return NO; -} - -- (BOOL)usingSecureTransportForTLS -{ - #if TARGET_OS_IPHONE - { - return ![self usingCFStreamForTLS]; - } - #endif - - return YES; -} - -- (void)suspendReadSource -{ - if (!(flags & kReadSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(readSource)"); - - dispatch_suspend(readSource); - flags |= kReadSourceSuspended; - } -} - -- (void)resumeReadSource -{ - if (flags & kReadSourceSuspended) - { - LogVerbose(@"dispatch_resume(readSource)"); - - dispatch_resume(readSource); - flags &= ~kReadSourceSuspended; - } -} - -- (void)suspendWriteSource -{ - if (!(flags & kWriteSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(writeSource)"); - - dispatch_suspend(writeSource); - flags |= kWriteSourceSuspended; - } -} - -- (void)resumeWriteSource -{ - if (flags & kWriteSourceSuspended) - { - LogVerbose(@"dispatch_resume(writeSource)"); - - dispatch_resume(writeSource); - flags &= ~kWriteSourceSuspended; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reading -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; -} - -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - if (length == 0) { - LogWarn(@"Cannot read: length == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)maxLength - tag:(long)tag -{ - if ([data length] == 0) { - LogWarn(@"Cannot read: [data length] == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - if (maxLength > 0 && maxLength < [data length]) { - LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:maxLength - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]]) - { - // We're not reading anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - // It's only possible to know the progress of our read if we're reading to a certain length. - // 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; - - if (tagPtr != NULL) *tagPtr = currentRead->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - if (total > 0) - result = (float)done / (float)total; - else - result = 1.0F; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * This method starts a new read, if needed. - * - * It is called when: - * - a user requests a read - * - after a read request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueRead -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // If we're not currently processing a read AND we have an available read stream - if ((currentRead == nil) && (flags & kConnected)) - { - if ([readQueue count] > 0) - { - // Dequeue the next object in the write queue - currentRead = [readQueue objectAtIndex:0]; - [readQueue removeObjectAtIndex:0]; - - - if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncReadPacket"); - - // Setup read timer (if needed) - [self setupReadTimerWithTimeout:currentRead->timeout]; - - // Immediately read, if possible - [self doReadData]; - } - } - else if (flags & kDisconnectAfterReads) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - else if (flags & kSocketSecure) - { - [self flushSSLBuffers]; - - // Edge case: - // - // We just drained all data from the ssl buffers, - // and all known data from the socket (socketFDBytesAvailable). - // - // If we didn't get any data from this process, - // then we may have reached the end of the TCP stream. - // - // Be sure callbacks are enabled so we're notified about a disconnection. - - if ([preBuffer availableBytes] == 0) - { - if ([self usingCFStreamForTLS]) { - // Callbacks never disabled - } - else { - [self resumeReadSource]; - } - } - } - } -} - -- (void)flushSSLBuffers -{ - LogTrace(); - - NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket"); - - if ([preBuffer availableBytes] > 0) - { - // Only flush the ssl buffers if the prebuffer is empty. - // This is to avoid growing the prebuffer inifinitely large. - - return; - } - -#if TARGET_OS_IPHONE - - if ([self usingCFStreamForTLS]) - { - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - CFIndex defaultBytesToRead = (1024 * 4); - - [preBuffer ensureCapacityForWrite:defaultBytesToRead]; - - uint8_t *buffer = [preBuffer writeBuffer]; - - CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); - LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result); - - if (result > 0) - { - [preBuffer didWrite:result]; - } - - flags &= ~kSecureSocketHasBytesAvailable; - } - - return; - } - -#endif -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - - __block NSUInteger estimatedBytesAvailable = 0; - - dispatch_block_t updateEstimatedBytesAvailable = ^{ - - // Figure out if there is any data available to be read - // - // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket - // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket - // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered - // - // We call the variable "estimated" because we don't know how many decrypted bytes we'll get - // from the encrypted bytes in the sslPreBuffer. - // However, we do know this is an upper bound on the estimation. - - estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes]; - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - }; - - updateEstimatedBytesAvailable(); - - if (estimatedBytesAvailable > 0) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); - - BOOL done = NO; - do - { - LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable); - - // Make sure there's enough room in the prebuffer - - [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; - - // Read data into prebuffer - - uint8_t *buffer = [preBuffer writeBuffer]; - size_t bytesRead = 0; - - OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead); - LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead); - - if (bytesRead > 0) - { - [preBuffer didWrite:bytesRead]; - } - - LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]); - - if (result != noErr) - { - done = YES; - } - else - { - updateEstimatedBytesAvailable(); - } - - } while (!done && estimatedBytesAvailable > 0); - } - -#endif -} - -- (void)doReadData -{ - LogTrace(); - - // This method is called on the socketQueue. - // It might be called directly, or via the readSource when data is available to be read. - - if ((currentRead == nil) || (flags & kReadsPaused)) - { - LogVerbose(@"No currentRead or kReadsPaused"); - - // Unable to read at this time - - if (flags & kSocketSecure) - { - // Here's the situation: - // - // We have an established secure connection. - // There may not be a currentRead, but there might be encrypted data sitting around for us. - // When the user does get around to issuing a read, that encrypted data will need to be decrypted. - // - // So why make the user wait? - // We might as well get a head start on decrypting some data now. - // - // The other reason we do this has to do with detecting a socket disconnection. - // The SSL/TLS protocol has it's own disconnection handshake. - // So when a secure socket is closed, a "goodbye" packet comes across the wire. - // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. - - [self flushSSLBuffers]; - } - - if ([self usingCFStreamForTLS]) - { - // CFReadStream only fires once when there is available data. - // It won't fire again until we've invoked CFReadStreamRead. - } - else - { - // If the readSource is firing, we need to pause it - // or else it will continue to fire over and over again. - // - // If the readSource is not firing, - // we want it to continue monitoring the socket. - - if (socketFDBytesAvailable > 0) - { - [self suspendReadSource]; - } - } - return; - } - - BOOL hasBytesAvailable; - unsigned long estimatedBytesAvailable; - - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Relegated to using CFStream... :( Boo! Give us a full SecureTransport stack Apple! - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - - #endif - } - else - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - estimatedBytesAvailable = socketFDBytesAvailable; - - if (flags & kSocketSecure) - { - // There are 2 buffers to be aware of here. - // - // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. - // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. - // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. - // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. - // - // The first buffer is one we create. - // SecureTransport often requests small amounts of data. - // This has to do with the encypted packets that are coming across the TCP stream. - // But it's non-optimal to do a bunch of small reads from the BSD socket. - // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) - // and may store excess in the sslPreBuffer. - - estimatedBytesAvailable += [sslPreBuffer availableBytes]; - - // The second buffer is within SecureTransport. - // As mentioned earlier, there are encrypted packets coming across the TCP stream. - // SecureTransport needs the entire packet to decrypt it. - // But if the entire packet produces X bytes of decrypted data, - // and we only asked SecureTransport for X/2 bytes of data, - // it must store the extra X/2 bytes of decrypted data for the next read. - // - // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. - // From the documentation: - // - // "This function does not block or cause any low-level read operations to occur." - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - } - - hasBytesAvailable = (estimatedBytesAvailable > 0); - - #endif - } - - if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) - { - LogVerbose(@"No data available to read..."); - - // No data available to read. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - } - return; - } - - if (flags & kStartingReadTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The readQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingWriteTLS) - { - if ([self usingSecureTransportForTLS]) - { - #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 - { - // We are still waiting for the writeQueue to drain and start the SSL/TLS process. - // We now know data is available to read. - - if (![self usingCFStreamForTLS]) - { - // Suspend the read source or else it will continue to fire nonstop. - - [self suspendReadSource]; - } - } - - return; - } - - BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occured - - NSUInteger totalBytesReadForCurrentRead = 0; - - // - // STEP 1 - READ FROM PREBUFFER - // - - if ([preBuffer availableBytes] > 0) - { - // 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. - - NSUInteger bytesToCopy; - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - } - else - { - // Read type #1 or #2 - - bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; - } - - // Make sure we have enough room in the buffer for our read. - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into packet buffer - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + - currentRead->bytesDone; - - memcpy(buffer, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the preBuffer - [preBuffer didRead:bytesToCopy]; - - LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]); - - // Update totals - - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done as soon as - // - we've read all available data (in prebuffer and socket) - // - we've read the maxLength of read packet. - - done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); - } - - } - - // - // STEP 2 - READ FROM SOCKET - // - - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && !waiting && 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 - - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); - } - else if (result == 0) - { - socketEOF = YES; - } - else - { - waiting = YES; - bytesRead = (size_t)result; - } - - // We only know how many decrypted bytes were read. - // The actual number of bytes read was likely more due to the overhead of the encryption. - // So we reset our flag, and rely on the next callback to alert us of more data. - flags &= ~kSecureSocketHasBytesAvailable; - - #endif - } - else - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // indicating that less data than requested was actually transferred" - // - // However, starting around 10.7, the function will sometimes return noErr, - // even if it didn't read as much data as requested. So we need to watch out for that. - - OSStatus result; - do - { - void *loop_buffer = buffer + bytesRead; - size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; - size_t loop_bytesRead = 0; - - result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)bytesRead); - - bytesRead += loop_bytesRead; - - } while ((result == noErr) && (bytesRead < bytesToRead)); - - - if (result != noErr) - { - if (result == errSSLWouldBlock) - waiting = YES; - else - { - if (result == errSSLClosedGraceful || result == errSSLClosedAbort) - { - // We've reached the end of the stream. - // Handle this the same way we would an EOF from the socket. - socketEOF = YES; - sslErrCode = result; - } - else - { - error = [self sslError:result]; - } - } - // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. - // This happens when the SSLRead function is able to read some data, - // but not the entire amount we requested. - - if (bytesRead <= 0) - { - bytesRead = 0; - } - } - - // Do not modify socketFDBytesAvailable. - // It will be updated via the SSLReadFunction(). - - #endif - } - } - else - { - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - if (result < 0) - { - if (errno == EWOULDBLOCK) - waiting = YES; - else - error = [self errnoErrorWithReason:@"Error in read() function"]; - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - socketEOF = YES; - socketFDBytesAvailable = 0; - } - else - { - bytesRead = result; - - if (bytesRead < bytesToRead) - { - // The read returned less data than requested. - // This means socketFDBytesAvailable was a bit off due to timing, - // because we read from the socket right when the readSource event was firing. - socketFDBytesAvailable = 0; - } - else - { - if (socketFDBytesAvailable <= bytesRead) - socketFDBytesAvailable = 0; - else - socketFDBytesAvailable -= bytesRead; - } - - if (socketFDBytesAvailable == 0) - { - waiting = YES; - } - } - } - - if (bytesRead > 0) - { - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - // - // Note: We should never be using a prebuffer when we're reading a specific length of data. - - NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (readIntoPreBuffer) - { - // We just read a big chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]); - - // Search for the terminating sequence - - bytesToRead = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToRead); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToRead]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToRead; - totalBytesReadForCurrentRead += bytesToRead; - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above - } - else - { - // We just read a big chunk of data directly into the packet's buffer. - // We need to move any overflow into the prebuffer. - - NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; - - if (overflow == 0) - { - // Perfect match! - // Every byte we read stays in the read buffer, - // and the last byte we read was the last byte of the term. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = YES; - } - else if (overflow > 0) - { - // The term was found within the data that we read, - // and there are extra bytes that extend past the end of the term. - // We need to move these excess bytes out of the read packet and into the prebuffer. - - NSInteger underflow = bytesRead - overflow; - - // Copy excess data into preBuffer - - LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow); - [preBuffer ensureCapacityForWrite:overflow]; - - uint8_t *overflowBuffer = buffer + underflow; - memcpy([preBuffer writeBuffer], overflowBuffer, overflow); - - [preBuffer didWrite:overflow]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Note: The completeCurrentRead method will trim the buffer for us. - - currentRead->bytesDone += underflow; - totalBytesReadForCurrentRead += underflow; - done = YES; - } - else - { - // The term was not found within the data that we read. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = NO; - } - } - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - - if (readIntoPreBuffer) - { - // We just read a chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - - // Now copy the data into the read packet. - // - // Recall that we didn't read directly into the packet's buffer to avoid - // over-allocating memory since we had no clue how much data was available to be read. - // - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesRead]; - - // Update totals - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - else - { - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - - done = YES; - } - - } // if (bytesRead > 0) - - } // if (!done && !error && !socketEOF && !waiting && hasBytesAvailable) - - - if (!done && currentRead->readLength == 0 && currentRead->term == nil) - { - // Read type #1 - read all available data - // - // We might arrive here if we read data from the prebuffer but not from the socket. - - done = (totalBytesReadForCurrentRead > 0); - } - - // Check to see if we're done, or if we've made progress - - if (done) - { - [self completeCurrentRead]; - - if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) - { - [self maybeDequeueRead]; - } - } - else if (totalBytesReadForCurrentRead > 0) - { - // We're not done read type #2 or #3 yet, but we have read in some bytes - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - __strong id theDelegate = delegate; - long theReadTag = currentRead->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; - }}); - } - } - - // Check for errors - - if (error) - { - [self closeWithError:error]; - } - else if (socketEOF) - { - [self doReadEOF]; - } - else if (waiting) - { - if (![self usingCFStreamForTLS]) - { - // Monitor the socket for readability (if we're not already doing so) - [self resumeReadSource]; - } - } - - // Do not add any code here without first adding return statements in the error cases above. -} - -- (void)doReadEOF -{ - LogTrace(); - - // This method may be called more than once. - // If the EOF is read while there is still data in the preBuffer, - // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. - - flags |= kSocketHasReadEOF; - - if (flags & kSocketSecure) - { - // If the SSL layer has any buffered data, flush it into the preBuffer now. - - [self flushSSLBuffers]; - } - - BOOL shouldDisconnect; - NSError *error = nil; - - if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) - { - // We received an EOF during or prior to startTLS. - // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. - - shouldDisconnect = YES; - - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - error = [self sslError:errSSLClosedAbort]; - #endif - } - } - else if (flags & kReadStreamClosed) - { - // The preBuffer has already been drained. - // The config allows half-duplex connections. - // We've previously checked the socket, and it appeared writeable. - // So we marked the read stream as closed and notified the delegate. - // - // As per the half-duplex contract, the socket will be closed when a write fails, - // or when the socket is manually closed. - - shouldDisconnect = NO; - } - else if ([preBuffer availableBytes] > 0) - { - LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); - - // Although we won't be able to read any more data from the socket, - // there is existing data that has been prebuffered that we can read. - - shouldDisconnect = NO; - } - else if (config & kAllowHalfDuplexConnection) - { - // We just received an EOF (end of file) from the socket's read stream. - // This means the remote end of the socket (the peer we're connected to) - // has explicitly stated that it will not be sending us any more data. - // - // 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; - - struct pollfd pfd[1]; - pfd[0].fd = socketFD; - pfd[0].events = POLLOUT; - pfd[0].revents = 0; - - poll(pfd, 1, 0); - - if (pfd[0].revents & POLLOUT) - { - // Socket appears to still be writeable - - shouldDisconnect = NO; - flags |= kReadStreamClosed; - - // Notify the delegate that we're going half-duplex - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - #endif - } - else - { - error = [self connectionClosedError]; - } - } - [self closeWithError:error]; - } - else - { - if (![self usingCFStreamForTLS]) - { - // Suspend the read source (if needed) - - [self suspendReadSource]; - } - } -} - -- (void)completeCurrentRead -{ - LogTrace(); - - NSAssert(currentRead, @"Trying to complete current read when there is no current read."); - - - NSData *result; - - if (currentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [currentRead->buffer setLength:currentRead->bytesDone]; - - result = currentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([currentRead->buffer length] > currentRead->originalBufferLength) - { - NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; - NSUInteger origSize = currentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); - - [currentRead->buffer setLength:buffSize]; - } - - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; - - result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; - } - - if (delegateQueue && [delegate 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 { - - [theDelegate socket:self didReadData:result withTag:theRead->tag]; - }}); - } - - [self endCurrentRead]; -} - -- (void)endCurrentRead -{ - if (readTimer) - { - dispatch_source_cancel(readTimer); - readTimer = NULL; - } - - currentRead = nil; -} - -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { - - [self doReadTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(readTimer); - } -} - -- (void)doReadTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kReadsPaused; - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - __strong id theDelegate = delegate; - GCDAsyncReadPacket *theRead = currentRead; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag - elapsed:theRead->timeout - bytesDone:theRead->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doReadTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doReadTimeoutWithExtension:0.0]; - } -} - -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentRead) - { - if (timeoutExtension > 0.0) - { - currentRead->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause reads, and continue - flags &= ~kReadsPaused; - [self doReadData]; - } - else - { - LogVerbose(@"ReadTimeout"); - - [self closeWithError:[self readTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if ([data length] == 0) return; - - GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites)) - { - [writeQueue addObject:packet]; - [self maybeDequeueWrite]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} - -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; - - dispatch_block_t block = ^{ - - if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) - { - // We're not writing anything right now. - - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; - - result = NAN; - } - else - { - NSUInteger done = currentWrite->bytesDone; - NSUInteger total = [currentWrite->buffer length]; - - if (tagPtr != NULL) *tagPtr = currentWrite->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; - - result = (float)done / (float)total; - } - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -/** - * Conditionally starts a new write. - * - * It is called when: - * - a user requests a write - * - after a write request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueWrite -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - // If we're not currently processing a write AND we have an available write stream - if ((currentWrite == nil) && (flags & kConnected)) - { - if ([writeQueue count] > 0) - { - // Dequeue the next object in the write queue - currentWrite = [writeQueue objectAtIndex:0]; - [writeQueue removeObjectAtIndex:0]; - - - if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncWritePacket"); - - // Setup write timer (if needed) - [self setupWriteTimerWithTimeout:currentWrite->timeout]; - - // Immediately write, if possible - [self doWriteData]; - } - } - else if (flags & kDisconnectAfterWrites) - { - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - } -} - -- (void)doWriteData -{ - LogTrace(); - - // This method is called by the writeSource via the socketQueue - - if ((currentWrite == nil) || (flags & kWritesPaused)) - { - LogVerbose(@"No currentWrite or kWritesPaused"); - - // Unable to write at this time - - if ([self usingCFStreamForTLS]) - { - // CFWriteStream only fires once when there is available data. - // It won't fire again until we've invoked CFWriteStreamWrite. - } - else - { - // If the writeSource is firing, we need to pause it - // or else it will continue to fire over and over again. - - if (flags & kSocketCanAcceptBytes) - { - [self suspendWriteSource]; - } - } - return; - } - - if (!(flags & kSocketCanAcceptBytes)) - { - LogVerbose(@"No space available to write..."); - - // No space available to write. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - } - return; - } - - if (flags & kStartingWriteTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The writeQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingReadTLS) - { - if ([self usingSecureTransportForTLS]) - { - #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 - { - // We are still waiting for the readQueue to drain and start the SSL/TLS process. - // We now know we can write to the socket. - - if (![self usingCFStreamForTLS]) - { - // Suspend the write source or else it will continue to fire nonstop. - - [self suspendWriteSource]; - } - } - - return; - } - - // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) - - BOOL waiting = NO; - NSError *error = nil; - size_t bytesWritten = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // - // Writing data using CFStream (over internal TLS) - // - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite); - LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); - } - else - { - bytesWritten = (size_t)result; - - // We always set waiting to true in this scenario. - // CFStream may have altered our underlying socket to non-blocking. - // Thus if we attempt to write without a callback, we may end up blocking our queue. - waiting = YES; - } - - #endif - } - 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) - // - // Parameters: - // context - An SSL session context reference. - // data - A pointer to the buffer of data to write. - // dataLength - The amount, in bytes, of data to write. - // processed - On return, the length, in bytes, of the data actually written. - // - // It sounds pretty straight-forward, - // but there are a few caveats you should be aware of. - // - // The SSLWrite method operates in a non-obvious (and rather annoying) manner. - // According to the documentation: - // - // Because you may configure the underlying connection to operate in a non-blocking manner, - // a write operation might return errSSLWouldBlock, indicating that less data than requested - // was actually transferred. In this case, you should repeat the call to SSLWrite until some - // other result is returned. - // - // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, - // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), - // but it sets processed to dataLength !! - // - // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, - // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to - // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. - // - // You might be wondering: - // If the SSLWrite function doesn't tell us how many bytes were written, - // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) - // for the next time we invoke SSLWrite? - // - // The answer is that SSLWrite cached all the data we told it to write, - // and it will push out that data next time we call SSLWrite. - // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. - // If we call SSLWrite with empty data, then it will simply push out the cached data. - // - // For this purpose we're going to break large writes into a series of smaller writes. - // This allows us to report progress back to the delegate. - - OSStatus result; - - BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); - BOOL hasNewDataToWrite = YES; - - if (hasCachedDataToWrite) - { - size_t processed = 0; - - result = SSLWrite(sslContext, NULL, 0, &processed); - - if (result == noErr) - { - bytesWritten = sslWriteCachedLength; - sslWriteCachedLength = 0; - - if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) - { - // We've written all data for the current write. - hasNewDataToWrite = NO; - } - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - } - else - { - error = [self sslError:result]; - } - - // Can't write any new data since we were unable to write the cached data. - hasNewDataToWrite = NO; - } - } - - if (hasNewDataToWrite) - { - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] - + currentWrite->bytesDone - + bytesWritten; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - size_t bytesRemaining = bytesToWrite; - - BOOL keepLooping = YES; - while (keepLooping) - { - size_t sslBytesToWrite = MIN(bytesRemaining, 32768); - size_t sslBytesWritten = 0; - - result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); - - if (result == noErr) - { - buffer += sslBytesWritten; - bytesWritten += sslBytesWritten; - bytesRemaining -= sslBytesWritten; - - keepLooping = (bytesRemaining > 0); - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - sslWriteCachedLength = sslBytesToWrite; - } - else - { - error = [self sslError:result]; - } - - keepLooping = NO; - } - - } // while (keepLooping) - - } // if (hasNewDataToWrite) - - #endif - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); - LogVerbose(@"wrote to socket = %zd", result); - - // Check results - if (result < 0) - { - if (errno == EWOULDBLOCK) - { - waiting = YES; - } - else - { - error = [self errnoErrorWithReason:@"Error in write() function"]; - } - } - else - { - bytesWritten = result; - } - } - - // We're done with our writing. - // If we explictly ran into a situation where the socket told us there was no room in the buffer, - // then we immediately resume listening for notifications. - // - // We must do this before we dequeue another write, - // as that may in turn invoke this method again. - // - // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - - if (waiting) - { - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - // Check our results - - BOOL done = NO; - - if (bytesWritten > 0) - { - // Update total amount read for the current write - currentWrite->bytesDone += bytesWritten; - LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone); - - // Is packet done? - done = (currentWrite->bytesDone == [currentWrite->buffer length]); - } - - if (done) - { - [self completeCurrentWrite]; - - if (!error) - { - [self maybeDequeueWrite]; - } - } - else - { - // 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) - { - // This would be the case if our write was able to accept some data, but not all of it. - - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - if (bytesWritten > 0) - { - // 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; - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; - }}); - } - } - } - - // Check for errors - - if (error) - { - [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]]; - } - - // Do not add any code here without first adding a return statement in the error case above. -} - -- (void)completeCurrentWrite -{ - LogTrace(); - - NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); - - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - __strong id theDelegate = delegate; - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWriteDataWithTag:theWriteTag]; - }}); - } - - [self endCurrentWrite]; -} - -- (void)endCurrentWrite -{ - if (writeTimer) - { - dispatch_source_cancel(writeTimer); - writeTimer = NULL; - } - - currentWrite = nil; -} - -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { - - [self doWriteTimeout]; - }}); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(writeTimer); - } -} - -- (void)doWriteTimeout -{ - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - - flags |= kWritesPaused; - - if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - __strong id theDelegate = delegate; - GCDAsyncWritePacket *theWrite = currentWrite; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - NSTimeInterval timeoutExtension = 0.0; - - timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag - elapsed:theWrite->timeout - bytesDone:theWrite->bytesDone]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doWriteTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doWriteTimeoutWithExtension:0.0]; - } -} - -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension -{ - if (currentWrite) - { - if (timeoutExtension > 0.0) - { - currentWrite->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - - // Unpause writes, and continue - flags &= ~kWritesPaused; - [self doWriteData]; - } - else - { - LogVerbose(@"WriteTimeout"); - - [self closeWithError:[self writeTimeoutError]]; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)startTLS:(NSDictionary *)tlsSettings -{ - LogTrace(); - - if (tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. - - tlsSettings = [NSDictionary dictionary]; - } - - GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites)) - { - [readQueue addObject:packet]; - [writeQueue addObject:packet]; - - flags |= kQueuedTLS; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - }}); - -} - -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling startTLS are complete - // - All queued writes prior to the user calling startTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - BOOL canUseSecureTransport = 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; - } - #endif - - if (IS_SECURE_TRANSPORT_AVAILABLE && canUseSecureTransport) - { - #if SECURE_TRANSPORT_MAYBE_AVAILABLE - [self ssl_startTLS]; - #endif - } - else - { - #if TARGET_OS_IPHONE - [self cf_startTLS]; - #endif - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#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); - - if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) - { - LogVerbose(@"%@ - No data available to read...", THIS_METHOD); - - // No data available to read. - // - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t totalBytesRead = 0; - size_t totalBytesLeftToBeRead = *bufferLength; - - BOOL done = NO; - BOOL socketError = NO; - - // - // STEP 1 : READ FROM SSL PRE BUFFER - // - - size_t sslPreBufferLength = [sslPreBuffer availableBytes]; - - if (sslPreBufferLength > 0) - { - LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); - - size_t bytesToCopy; - if (sslPreBufferLength > totalBytesLeftToBeRead) - bytesToCopy = totalBytesLeftToBeRead; - else - bytesToCopy = sslPreBufferLength; - - LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - - // - // STEP 2 : READ FROM SOCKET - // - - if (!done && (socketFDBytesAvailable > 0)) - { - LogVerbose(@"%@: Reading from socket...", THIS_METHOD); - - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; - - BOOL readIntoPreBuffer; - size_t bytesToRead; - uint8_t *buf; - - if (socketFDBytesAvailable > totalBytesLeftToBeRead) - { - // Read all available data from socket into sslPreBuffer. - // Then copy requested amount into dataBuffer. - - LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); - - [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; - - readIntoPreBuffer = YES; - bytesToRead = (size_t)socketFDBytesAvailable; - buf = [sslPreBuffer writeBuffer]; - } - else - { - // Read available data from socket directly into dataBuffer. - - LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); - - readIntoPreBuffer = NO; - bytesToRead = totalBytesLeftToBeRead; - buf = (uint8_t *)buffer + totalBytesRead; - } - - ssize_t result = read(socketFD, buf, bytesToRead); - LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); - - if (result < 0) - { - LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); - - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - LogVerbose(@"%@: read EOF", THIS_METHOD); - - socketError = YES; - socketFDBytesAvailable = 0; - } - else - { - size_t bytesReadFromSocket = result; - - if (socketFDBytesAvailable > bytesReadFromSocket) - socketFDBytesAvailable -= bytesReadFromSocket; - else - socketFDBytesAvailable = 0; - - if (readIntoPreBuffer) - { - [sslPreBuffer didWrite:bytesReadFromSocket]; - - size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); - - LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy); - - memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; - - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; - - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - } - else - { - totalBytesRead += bytesReadFromSocket; - totalBytesLeftToBeRead -= bytesReadFromSocket; - } - - done = (totalBytesLeftToBeRead == 0); - - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - } - - *bufferLength = totalBytesRead; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength -{ - if (!(flags & kSocketCanAcceptBytes)) - { - // Unable to write. - // - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - - *bufferLength = 0; - return errSSLWouldBlock; - } - - size_t bytesToWrite = *bufferLength; - size_t bytesWritten = 0; - - BOOL done = NO; - BOOL socketError = NO; - - int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD; - - ssize_t result = write(socketFD, buffer, bytesToWrite); - - if (result < 0) - { - if (errno != EWOULDBLOCK) - { - socketError = YES; - } - - flags &= ~kSocketCanAcceptBytes; - } - else if (result == 0) - { - flags &= ~kSocketCanAcceptBytes; - } - else - { - bytesWritten = result; - - done = (bytesWritten == bytesToWrite); - } - - *bufferLength = bytesWritten; - - if (done) - return noErr; - - if (socketError) - return errSSLClosedAbort; - - return errSSLWouldBlock; -} - -static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslReadWithBuffer:data length:dataLength]; -} - -static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); - - return [asyncSocket sslWriteWithBuffer:data length:dataLength]; -} - -- (void)ssl_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via SecureTransport)..."); - - OSStatus status; - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue]; - - #if TARGET_OS_IPHONE - { - if (isServer) - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); - else - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); - - if (sslContext == NULL) - { - [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; - return; - } - } - #else - { - status = SSLNewContext(isServer, &sslContext); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; - return; - } - } - #endif - - status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; - return; - } - - status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; - return; - } - - // 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) - - id value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName]; - if ([value isKindOfClass:[NSString class]]) - { - NSString *peerName = (NSString *)value; - - const char *peer = [peerName UTF8String]; - size_t peerLen = strlen(peer); - - status = SSLSetPeerDomainName(sslContext, peer, peerLen); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; - return; - } - } - - // 2. kCFStreamSSLAllowsAnyRoot - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsAnyRoot"); - #else - - BOOL allowsAnyRoot = [value boolValue]; - - status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]]; - return; - } - - #endif - } - - // 3. kCFStreamSSLAllowsExpiredRoots - - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots]; - if (value) - { - #if TARGET_OS_IPHONE - NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredRoots"); - #else - - 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); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - - // 7. kCFStreamSSLLevel - - #if TARGET_OS_IPHONE - { - NSString *sslLevel = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - - NSString *sslMinLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - NSString *sslMaxLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - - if (sslLevel) - { - 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"); - } - } - } - - if (sslMinLevel || sslMaxLevel) - { - 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) - { - status1 = SSLSetProtocolVersionMin(sslContext, minProtocol); - } - if (maxProtocol != kSSLProtocolUnknown) - { - status2 = SSLSetProtocolVersionMax(sslContext, maxProtocol); - } - - if (status1 != noErr || status2 != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMinMax"]]; - return; - } - } - } - #else - { - value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel]; - if (value) - { - NSString *sslLevel = (NSString *)value; - - OSStatus status1 = noErr; - OSStatus status2 = noErr; - OSStatus status3 = noErr; - - if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2]) - { - // 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"]]; - return; - } - } - } - #endif - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if (value) - { - NSArray *cipherSuites = (NSArray *)value; - NSUInteger numberCiphers = [cipherSuites count]; - SSLCipherSuite ciphers[numberCiphers]; - - NSUInteger cipherIndex; - for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) - { - NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = [cipherObject shortValue]; - } - - status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; - return; - } - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - - #if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if (value) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - #endif - - // Setup the sslPreBuffer - // - // Any data in the preBuffer needs to be moved into the sslPreBuffer, - // as this data is now part of the secure read stream. - - sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - - size_t preBufferLength = [preBuffer availableBytes]; - - if (preBufferLength > 0) - { - [sslPreBuffer ensureCapacityForWrite:preBufferLength]; - - memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); - [preBuffer didRead:preBufferLength]; - [sslPreBuffer didWrite:preBufferLength]; - } - - sslErrCode = noErr; - - // Start the SSL Handshake process - - [self ssl_continueSSLHandshake]; -} - -- (void)ssl_continueSSLHandshake -{ - LogTrace(); - - // 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. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -- (void)cf_finishSSLHandshake -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)]) - { - __strong id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } -} - -- (void)cf_abortSSLHandshake:(NSError *)error -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - [self closeWithError:error]; - } -} - -- (void)cf_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via CFStream)..."); - - if ([preBuffer availableBytes] > 0) - { - NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - [self suspendReadSource]; - [self suspendWriteSource]; - - socketFDBytesAvailable = 0; - flags &= ~kSocketCanAcceptBytes; - flags &= ~kSecureSocketHasBytesAvailable; - - flags |= kUsingCFStreamForTLS; - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:YES]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS"); - NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS"); - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; - - // Getting an error concerning kCFStreamPropertySSLSettings ? - // You need to add the CFNetwork framework to your iOS application. - - BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings); - BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings); - - // For some reason, starting around the time of iOS 4.3, - // the first call to set the kCFStreamPropertySSLSettings will return true, - // but the second will return false. - // - // Order doesn't seem to matter. - // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. - // Either way, the first call will return true, and the second returns false. - // - // Interestingly, this doesn't seem to affect anything. - // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) - // setting it on one side of the stream automatically sets it for the other side of the stream. - // - // Although there isn't anything in the documentation to suggest that the second attempt would fail. - // - // Furthermore, this only seems to affect streams that are negotiating a security upgrade. - // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure - // connection, and then a startTLS is issued. - // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). - - if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. - { - [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; - return; - } - - LogVerbose(@"Waiting for SSL Handshake to complete..."); -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -+ (void)startCFStreamThreadIfNeeded -{ - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread) - object:nil]; - [cfstreamThread start]; - }); -} - -+ (void)cfstreamThread { @autoreleasepool -{ - [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; - - LogInfo(@"CFStreamThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // 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:) - userInfo:nil - repeats:YES]; - - [[NSRunLoop currentRunLoop] run]; - - LogInfo(@"CFStreamThread: Stopped"); -}} - -+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncSocket->readStream) - CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); - - if (asyncSocket->writeStream) - CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventHasBytesAvailable: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket doReadData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - Other"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; - - switch(type) - { - case kCFStreamEventCanAcceptBytes: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket doWriteData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - Other"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } - -} - -- (BOOL)createReadAndWriteStream -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (readStream || writeStream) - { - // Streams already created - return YES; - } - - int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD; - - if (socketFD == SOCKET_NULL) - { - // Cannot create streams without a file descriptor - return NO; - } - - if (![self isConnected]) - { - // Cannot create streams until file descriptor is connected - return NO; - } - - LogVerbose(@"Creating read and write stream..."); - - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream); - - // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). - // But let's not take any chances. - - if (readStream) - CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - if (writeStream) - CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - - if ((readStream == NULL) || (writeStream == NULL)) - { - LogWarn(@"Unable to create read and write stream..."); - - if (readStream) - { - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - - return NO; - } - - return YES; -} - -- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite -{ - LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - streamContext.version = 0; - streamContext.info = (__bridge void *)(self); - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - readStreamEvents |= kCFStreamEventHasBytesAvailable; - - if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)) - { - return NO; - } - - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - writeStreamEvents |= kCFStreamEventCanAcceptBytes; - - if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)) - { - return NO; - } - - return YES; -} - -- (BOOL)addStreamsToRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (!(flags & kAddedStreamsToRunLoop)) - { - LogVerbose(@"Adding streams to runloop..."); - - [[self class] startCFStreamThreadIfNeeded]; - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - - flags |= kAddedStreamsToRunLoop; - } - - return YES; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - if (flags & kAddedStreamsToRunLoop) - { - LogVerbose(@"Removing streams from runloop..."); - - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - - flags &= ~kAddedStreamsToRunLoop; - } -} - -- (BOOL)openStreams -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); - CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); - - if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) - { - LogVerbose(@"Opening read and write stream..."); - - BOOL r1 = CFReadStreamOpen(readStream); - BOOL r2 = CFWriteStreamOpen(writeStream); - - if (!r1 || !r2) - { - LogError(@"Error in CFStreamOpen"); - return NO; - } - } - - return YES; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See header file for big discussion of this method. -**/ -- (BOOL)autoDisconnectOnClosedReadStream -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kAllowHalfDuplexConnection) == 0); - } - else - { - __block BOOL result; - - dispatch_sync(socketQueue, ^{ - result = ((config & kAllowHalfDuplexConnection) == 0); - }); - - return result; - } -} - -/** - * See header file for big discussion of this method. -**/ -- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag -{ - // Note: YES means kAllowHalfDuplexConnection is OFF - - dispatch_block_t block = ^{ - - if (flag) - config &= ~kAllowHalfDuplexConnection; - else - config |= kAllowHalfDuplexConnection; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - - -/** - * See header file for big discussion of this method. -**/ -- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue -{ - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue -{ - dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); -} - -/** - * See header file for big discussion of this method. -**/ -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socketFD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket4FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -/** - * Questions? Have you read the header file? -**/ -- (int)socket6FD -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -/** - * Questions? Have you read the header file? -**/ -- (CFReadStreamRef)readStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (readStream == NULL) - [self createReadAndWriteStream]; - - return readStream; -} - -/** - * Questions? Have you read the header file? -**/ -- (CFWriteStreamRef)writeStream -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - if (writeStream == NULL) - [self createReadAndWriteStream]; - - return writeStream; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat -{ - if (![self createReadAndWriteStream]) - { - // Error occured creating streams (perhaps socket isn't open) - return NO; - } - - BOOL r1, r2; - - LogVerbose(@"Enabling backgrouding on socket"); - - r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - - if (!r1 || !r2) - { - return NO; - } - - if (!caveat) - { - if (![self openStreams]) - { - return NO; - } - } - - return YES; -} - -/** - * Questions? Have you read the header file? -**/ -- (BOOL)enableBackgroundingOnSocket -{ - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:NO]; -} - -- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? -{ - // This method was created as a workaround for a bug in iOS. - // Apple has since fixed this bug. - // I'm not entirely sure which version of iOS they fixed it in... - - LogTrace(); - - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } - - return [self enableBackgroundingOnSocketWithCaveat:YES]; -} - -#endif - -#if SECURE_TRANSPORT_MAYBE_AVAILABLE - -- (SSLContextRef)sslContext -{ - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } - - return sslContext; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host; - - if ([self getHost:&host port:NULL fromAddress:address]) - return host; - else - return nil; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port; - - if ([self getHost:NULL port:&port fromAddress:address]) - return port; - else - return 0; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - struct sockaddr_in sockaddr4; - memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; - - return YES; - } - } - else if (sockaddrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - struct sockaddr_in6 sockaddr6; - memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; - - return YES; - } - } - } - - return NO; -} - -+ (NSData *)CRLFData -{ - return [NSData dataWithBytes:"\x0D\x0A" length:2]; -} - -+ (NSData *)CRData -{ - return [NSData dataWithBytes:"\x0D" length:1]; -} - -+ (NSData *)LFData -{ - return [NSData dataWithBytes:"\x0A" length:1]; -} - -+ (NSData *)ZeroData -{ - return [NSData dataWithBytes:"" length:1]; -} - -@end diff --git a/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h b/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h deleted file mode 100644 index 86882db..0000000 --- a/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h +++ /dev/null @@ -1,920 +0,0 @@ -// -// GCDAsyncUdpSocket -// -// This class is in the public domain. -// Originally created by Robbie Hanson of Deusty LLC. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#import -#import - - -extern NSString *const GCDAsyncUdpSocketException; -extern NSString *const GCDAsyncUdpSocketErrorDomain; - -extern NSString *const GCDAsyncUdpSocketQueueName; -extern NSString *const GCDAsyncUdpSocketThreadName; - -enum GCDAsyncUdpSocketError -{ - GCDAsyncUdpSocketNoError = 0, // Never used - GCDAsyncUdpSocketBadConfigError, // Invalid configuration - GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed - GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out - GCDAsyncUdpSocketClosedError, // The socket was closed - GCDAsyncUdpSocketOtherError, // Description provided in userInfo -}; -typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError; - -/** - * You may optionally set a receive filter for the socket. - * A filter can provide several useful features: - * - * 1. Many times udp packets need to be parsed. - * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. - * The end result is a parallel socket io, datagram parsing, and packet processing. - * - * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. - * The filter can prevent such packets from arriving at the delegate. - * And because the filter can run in its own independent queue, this doesn't slow down the delegate. - * - * - Since the udp protocol does not guarantee delivery, udp packets may be lost. - * Many protocols built atop udp thus provide various resend/re-request algorithms. - * This sometimes results in duplicate packets arriving. - * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. - * - * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. - * Such packets need to be ignored. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * @param data - The packet that was received. - * @param address - The address the data was received from. - * See utilities section for methods to extract info from address. - * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. - * For example, filter block can parse the data and then, - * pass the parsed data to the delegate. - * - * @returns - YES if the received packet should be passed onto the delegate. - * NO if the received packet should be discarded, and not reported to the delegete. - * - * Example: - * - * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { - * - * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; - * - * *context = response; - * return (response != nil); - * }; - * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; - * -**/ -typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id *context); - -/** - * You may optionally set a send filter for the socket. - * A filter can provide several interesting possibilities: - * - * 1. Optional caching of resolved addresses for domain names. - * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. - * - * 2. Reusable modules of code for bandwidth monitoring. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * @param data - The packet that was received. - * @param address - The address the data was received from. - * See utilities section for methods to extract info from address. - * @param tag - The tag that was passed in the send method. - * - * @returns - YES if the packet should actually be sent over the socket. - * NO if the packet should be silently dropped (not sent over the socket). - * - * Regardless of the return value, the delegate will be informed that the packet was successfully sent. - * -**/ -typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); - - -@interface GCDAsyncUdpSocket : NSObject - -/** - * GCDAsyncUdpSocket uses the standard delegate paradigm, - * but executes all delegate callbacks on a given delegate dispatch queue. - * This allows for maximum concurrency, while at the same time providing easy thread safety. - * - * You MUST set a delegate AND delegate dispatch queue before attempting to - * use the socket, or you will get an error. - * - * The socket queue is optional. - * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. - * If you choose to provide a socket queue, the socket queue must not be a concurrent queue. - * - * 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; - -#pragma mark Configuration - -- (id)delegate; -- (void)setDelegate:(id)delegate; -- (void)synchronouslySetDelegate:(id)delegate; - -- (dispatch_queue_t)delegateQueue; -- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue; -- (void)synchronouslySetDelegateQueue:(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; - -/** - * By default, both IPv4 and IPv6 are enabled. - * - * This means GCDAsyncUdpSocket automatically supports both protocols, - * and can send to IPv4 or IPv6 addresses, - * as well as receive over IPv4 and IPv6. - * - * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. - * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. - * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. - * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. - * If IPv4 is preferred, then IPv4 is used. - * If IPv6 is preferred, then IPv6 is used. - * If neutral, then the first IP version in the resolved array will be used. - * - * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. - * On prior systems the default IP preference is IPv4. - **/ -- (BOOL)isIPv4Enabled; -- (void)setIPv4Enabled:(BOOL)flag; - -- (BOOL)isIPv6Enabled; -- (void)setIPv6Enabled:(BOOL)flag; - -- (BOOL)isIPv4Preferred; -- (BOOL)isIPv6Preferred; -- (BOOL)isIPVersionNeutral; - -- (void)setPreferIPv4; -- (void)setPreferIPv6; -- (void)setIPVersionNeutral; - -/** - * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. - * The default maximum size is 9216 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. - * - * Since the OS/GCD notifies us of the size of each received UDP packet, - * the actual allocated buffer size for each packet is exact. - * And in practice the size of UDP packets is generally much smaller than the max. - * Indeed most protocols will send and receive packets of only a few bytes, - * or will set a limit on the size of packets to prevent fragmentation in the IP layer. - * - * If you set the buffer size too small, the sockets API in the OS will silently discard - * any extra data, and you will not be notified of the error. -**/ -- (uint16_t)maxReceiveIPv4BufferSize; -- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; - -- (uint32_t)maxReceiveIPv6BufferSize; -- (void)setMaxReceiveIPv6BufferSize:(uint32_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; - -#pragma mark Diagnostics - -/** - * Returns the local address info for the socket. - * - * The localAddress method returns a sockaddr structure wrapped in a NSData object. - * The localHost method returns the human readable IP address as a string. - * - * 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; -- (uint16_t)localPort; - -- (NSData *)localAddress_IPv4; -- (NSString *)localHost_IPv4; -- (uint16_t)localPort_IPv4; - -- (NSData *)localAddress_IPv6; -- (NSString *)localHost_IPv6; -- (uint16_t)localPort_IPv6; - -/** - * Returns the remote address info for the socket. - * - * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. - * The connectedHost method returns the human readable IP address as a string. - * - * Note: Since UDP is connectionless by design, connected address info - * 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; -- (uint16_t)connectedPort; - -/** - * Returns whether or not this socket has been connected to a single host. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * If connected, the socket will only be able to send/receive data to/from the connected host. -**/ -- (BOOL)isConnected; - -/** - * Returns whether or not this socket has been closed. - * The only way a socket can be closed is if you explicitly call one of the close methods. -**/ -- (BOOL)isClosed; - -/** - * Returns whether or not this socket is IPv4. - * - * By default this will be true, unless: - * - IPv4 is disabled (via setIPv4Enabled:) - * - The socket is explicitly bound to an IPv6 address - * - The socket is connected to an IPv6 address -**/ -- (BOOL)isIPv4; - -/** - * Returns whether or not this socket is IPv6. - * - * By default this will be true, unless: - * - IPv6 is disabled (via setIPv6Enabled:) - * - The socket is explicitly bound to an IPv4 address - * _ The socket is connected to an IPv4 address - * - * This method will also return false on platforms that do not support IPv6. - * Note: The iPhone does not currently support IPv6. -**/ -- (BOOL)isIPv6; - -#pragma mark Binding - -/** - * Binds the UDP socket to the given port. - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You may optionally pass a port number of zero to immediately bind the socket, - * yet still allow the OS to automatically assign an available port. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * 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 error:(NSError **)errPtr; - -/** - * Binds the UDP socket to the given port and optional interface. - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You may optionally pass a port number of zero to immediately bind the socket, - * yet still allow the OS to automatically assign an available port. - * - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept packets from the local machine. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * 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; - -/** - * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * 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)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * Connects the UDP socket to the given host and port. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * - * Choosing to connect to a specific host/port has the following effect: - * - You will only be able to send data to the connected host/port. - * - You will only be able to receive data from the connected host/port. - * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". - * - * The actual process of connecting a UDP socket does not result in any communication on the socket. - * It simply changes the internal state of the socket. - * - * You cannot bind a socket after it has been connected. - * You can only connect a socket once. - * - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * - * This method is asynchronous as it requires a DNS lookup to resolve the given host name. - * If an obvious error is detected, this method immediately returns NO and sets errPtr. - * If you don't care about the error, you can pass nil for errPtr. - * Otherwise, this method returns YES and begins the asynchronous connection process. - * The result of the asynchronous connection process will be reported via the delegate methods. - **/ -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; - -/** - * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; - * - * By design, UDP is a connectionless protocol, and connecting is not needed. - * - * Choosing to connect to a specific address has the following effect: - * - You will only be able to send data to the connected address. - * - You will only be able to receive data from the connected address. - * - You will receive ICMP messages that come from the connected address, such as "connection refused". - * - * Connecting a UDP socket does not result in any communication on the socket. - * It simply changes the internal state of the socket. - * - * You cannot bind a socket after its been connected. - * You can only connect a socket once. - * - * On success, returns YES. - * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. - * - * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. - * Thus when this method returns, the connection has either failed or fully completed. - * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. - * However, for compatibility and simplification of delegate code, if this method returns YES - * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -#pragma mark Multicast - -/** - * Join multicast group. - * Group should be an IP address (eg @"225.228.0.1"). - * - * 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 error:(NSError **)errPtr; - -/** - * Join multicast group. - * Group should be an IP address (eg @"225.228.0.1"). - * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). - * - * 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)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; - -#pragma mark Broadcast - -/** - * By default, the underlying socket in the OS will not allow you to send broadcast messages. - * In order to send broadcast messages, you need to enable this functionality in the socket. - * - * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is - * delivered to every host on the network. - * The reason this is generally disabled by default (by the OS) is to prevent - * accidental broadcast messages from flooding the network. -**/ -- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; - -#pragma mark Sending - -/** - * Asynchronously sends the given data, with the given timeout and tag. - * - * This method may only be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending 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)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. - * - * This method cannot be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param host - * The destination to send the udp packet to. - * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). - * You may also use the convenience strings of "loopback" or "localhost". - * - * @param port - * The port of the host to send to. - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending 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)sendData:(NSData *)data - toHost:(NSString *)host - port:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given address. - * - * This method cannot be used with a connected socket. - * Recall that connecting is optional for a UDP socket. - * For connected sockets, data can only be sent to the connected address. - * For non-connected sockets, the remote destination is specified for each packet. - * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. - * - * @param data - * The data to send. - * If data is nil or zero-length, this method does nothing. - * If passing NSMutableData, please read the thread-safety notice below. - * - * @param address - * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). - * - * @param timeout - * The timeout for the send opeartion. - * If the timeout value is negative, the send operation will not use a timeout. - * - * @param tag - * The tag is for your convenience. - * It is not sent or received over the socket in any manner what-so-ever. - * It is reported back as a parameter in the udpSocket:didSendDataWithTag: - * or udpSocket:didNotSendDataWithTag:dueToError: methods. - * You can use it as an array index, state id, type constant, etc. - * - * - * Thread-Safety Note: - * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while - * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method - * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying - * that this particular send operation has completed. - * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. - * It simply retains it for performance reasons. - * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. - * Copying this data adds an unwanted/unneeded overhead. - * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket - * completes sending 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)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * You may optionally set a send filter for the socket. - * A filter can provide several interesting possibilities: - * - * 1. Optional caching of resolved addresses for domain names. - * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. - * - * 2. Reusable modules of code for bandwidth monitoring. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. - * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. - * - * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), - * passing YES for the isAsynchronous parameter. -**/ -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; - -/** - * The receive filter can be run via dispatch_async or dispatch_sync. - * Most typical situations call for asynchronous operation. - * - * However, there are a few situations in which synchronous operation is preferred. - * Such is the case when the filter is extremely minimal and fast. - * This is because dispatch_sync is faster than dispatch_async. - * - * If you choose synchronous operation, be aware of possible deadlock conditions. - * Since the socket queue is executing your block via dispatch_sync, - * 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 - isAsynchronous:(BOOL)isAsynchronous; - -#pragma mark Receiving - -/** - * There are two modes of operation for receiving packets: one-at-a-time & continuous. - * - * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. - * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, - * where your state machine may not always be ready to process incoming packets. - * - * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. - * Receiving packets continuously is better suited to real-time streaming applications. - * - * You may switch back and forth between one-at-a-time mode and continuous mode. - * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. - * - * When a packet is received (and not filtered by the optional receive filter), - * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. - * - * If the socket is able to begin receiving packets, this method returns YES. - * Otherwise it returns NO, and sets the errPtr with appropriate error information. - * - * An example error: - * You created a udp socket to act as a server, and immediately called receive. - * You forgot to first bind the socket to a port number, and received a error with a message like: - * "Must bind socket before you can receive data." -**/ -- (BOOL)receiveOnce:(NSError **)errPtr; - -/** - * There are two modes of operation for receiving packets: one-at-a-time & continuous. - * - * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. - * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, - * where your state machine may not always be ready to process incoming packets. - * - * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. - * Receiving packets continuously is better suited to real-time streaming applications. - * - * You may switch back and forth between one-at-a-time mode and continuous mode. - * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. - * - * For every received packet (not filtered by the optional receive filter), - * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. - * - * If the socket is able to begin receiving packets, this method returns YES. - * Otherwise it returns NO, and sets the errPtr with appropriate error information. - * - * An example error: - * You created a udp socket to act as a server, and immediately called receive. - * You forgot to first bind the socket to a port number, and received a error with a message like: - * "Must bind socket before you can receive data." -**/ -- (BOOL)beginReceiving:(NSError **)errPtr; - -/** - * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. - * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. - * - * Important Note: - * GCDAsyncUdpSocket may be running in parallel with your code. - * That is, your delegate is likely running on a separate thread/dispatch_queue. - * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. - * Thus, if those delegate methods have already been dispatch_async'd, - * your didReceive delegate method may still be invoked after this method has been called. - * You should be aware of this, and program defensively. -**/ -- (void)pauseReceiving; - -/** - * You may optionally set a receive filter for the socket. - * This receive filter may be set to run in its own queue (independent of delegate queue). - * - * A filter can provide several useful features. - * - * 1. Many times udp packets need to be parsed. - * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. - * The end result is a parallel socket io, datagram parsing, and packet processing. - * - * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. - * The filter can prevent such packets from arriving at the delegate. - * And because the filter can run in its own independent queue, this doesn't slow down the delegate. - * - * - Since the udp protocol does not guarantee delivery, udp packets may be lost. - * Many protocols built atop udp thus provide various resend/re-request algorithms. - * This sometimes results in duplicate packets arriving. - * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. - * - * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. - * Such packets need to be ignored. - * - * 3. Sometimes traffic shapers are needed to simulate real world environments. - * A filter allows you to write custom code to simulate such environments. - * The ability to code this yourself is especially helpful when your simulated environment - * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), - * or the system tools to handle this aren't available (e.g. on a mobile device). - * - * Example: - * - * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { - * - * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; - * - * *context = response; - * return (response != nil); - * }; - * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; - * - * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. - * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. - * - * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), - * passing YES for the isAsynchronous parameter. -**/ -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue; - -/** - * The receive filter can be run via dispatch_async or dispatch_sync. - * Most typical situations call for asynchronous operation. - * - * However, there are a few situations in which synchronous operation is preferred. - * Such is the case when the filter is extremely minimal and fast. - * This is because dispatch_sync is faster than dispatch_async. - * - * If you choose synchronous operation, be aware of possible deadlock conditions. - * Since the socket queue is executing your block via dispatch_sync, - * 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 - isAsynchronous:(BOOL)isAsynchronous; - -#pragma mark Closing - -/** - * Immediately closes the underlying socket. - * Any pending send operations are discarded. - * - * The GCDAsyncUdpSocket instance may optionally be used again. - * (it will setup/configure/use another unnderlying BSD socket). -**/ -- (void)close; - -/** - * Closes the underlying socket after all pending send operations have been sent. - * - * The GCDAsyncUdpSocket instance may optionally be used again. - * (it will setup/configure/use another unnderlying BSD socket). -**/ -- (void)closeAfterSending; - -#pragma mark Advanced - -/** - * It's not thread-safe to access certain variables from outside the socket's internal queue. - * - * For example, the socket file descriptor. - * File descriptors are simply integers which reference an index in the per-process file table. - * However, when one requests a new file descriptor (by opening a file or socket), - * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. - * So if we're not careful, the following could be possible: - * - * - Thread A invokes a method which returns the socket's file descriptor. - * - The socket is closed via the socket's internal queue on thread B. - * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. - * - Thread A is now accessing/altering the file instead of the socket. - * - * In addition to this, other variables are not actually objects, - * and thus cannot be retained/released or even autoreleased. - * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. - * - * Although there are internal variables that make it difficult to maintain thread-safety, - * it is important to provide access to these variables - * to ensure this class can be used in a wide array of environments. - * This method helps to accomplish this by invoking the current block on the socket's internal queue. - * The methods below can be invoked from within the block to access - * those generally thread-unsafe internal variables in a thread-safe manner. - * The given block will be invoked synchronously on the socket's internal queue. - * - * If you save references to any protected variables and use them outside the block, you do so at your own peril. -**/ -- (void)performBlock:(dispatch_block_t)block; - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Provides access to the socket's file descriptor(s). - * If the socket isn't connected, or explicity bound to a particular interface, - * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. -**/ -- (int)socketFD; -- (int)socket4FD; -- (int)socket6FD; - -#if TARGET_OS_IPHONE - -/** - * These methods are only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. - * - * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) - * 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; - -/** - * This method is only available from within the context of a performBlock: invocation. - * See the documentation for the performBlock: method above. - * - * Configures the socket to allow it to operate when the iOS application has been backgrounded. - * In other words, this method creates a read & write stream, and invokes: - * - * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - * - * Returns YES if successful, NO otherwise. - * - * Example usage: - * - * [asyncUdpSocket performBlock:^{ - * [asyncUdpSocket enableBackgroundingOnSocket]; - * }]; - * - * - * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). -**/ -//- (BOOL)enableBackgroundingOnSockets; - -#endif - -#pragma mark Utilities - -/** - * Extracting host/port/family information from raw address data. -**/ - -+ (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; - -@end - diff --git a/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m b/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m deleted file mode 100644 index 4ce870e..0000000 --- a/Pods/CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m +++ /dev/null @@ -1,5348 +0,0 @@ -// -// GCDAsyncUdpSocket -// -// This class is in the public domain. -// Originally created by Robbie Hanson of Deusty LLC. -// Updated and maintained by Deusty LLC and the Apple development community. -// -// https://github.com/robbiehanson/CocoaAsyncSocket -// - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -// 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 - -#import "GCDAsyncUdpSocket.h" - -#if TARGET_OS_IPHONE - #import - #import -#endif - -#import -#import -#import -#import -#import -#import -#import - - -#if 0 - -// Logging Enabled - See log level below - -// Logging uses the CocoaLumberjack framework (which is also GCD based). -// http://code.google.com/p/cocoalumberjack/ -// -// It allows us to do a lot of logging without significantly slowing down the code. -#import "DDLog.h" - -#define LogAsync NO -#define LogContext 65535 - -#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__) - -#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) - -#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) -#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) - -// Log levels : off, error, warn, info, verbose -static const int logLevel = LOG_LEVEL_VERBOSE; - -#else - -// Logging Disabled - -#define LogError(frmt, ...) {} -#define LogWarn(frmt, ...) {} -#define LogInfo(frmt, ...) {} -#define LogVerbose(frmt, ...) {} - -#define LogCError(frmt, ...) {} -#define LogCWarn(frmt, ...) {} -#define LogCInfo(frmt, ...) {} -#define LogCVerbose(frmt, ...) {} - -#define LogTrace() {} -#define LogCTrace(frmt, ...) {} - -#endif - -/** - * Seeing a return statements within an inner block - * can sometimes be mistaken for a return point of the enclosing method. - * This makes inline blocks a bit easier to read. -**/ -#define return_from_block return - -/** - * A socket file descriptor is really just an integer. - * It represents the index of the socket within the kernel. - * This makes invalid file descriptor comparisons easier to read. -**/ -#define SOCKET_NULL -1 - -/** - * Just to type less code. -**/ -#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} - - -@class GCDAsyncUdpSendPacket; - -NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException"; -NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain"; - -NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket"; -NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream"; - -enum GCDAsyncUdpSocketFlags -{ - kDidCreateSockets = 1 << 0, // If set, the sockets have been created. - kDidBind = 1 << 1, // If set, bind has been called. - kConnecting = 1 << 2, // If set, a connection attempt is in progress. - kDidConnect = 1 << 3, // If set, socket is connected. - kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled - kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled - kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. - kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. - kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. - kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. - kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. - kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. - kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. - kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. - kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. - kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. - kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. -#if TARGET_OS_IPHONE - kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread -#endif -}; - -enum GCDAsyncUdpSocketConfig -{ - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 - kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncUdpSocket () -{ - id delegate; - dispatch_queue_t delegateQueue; - - GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; - dispatch_queue_t receiveFilterQueue; - BOOL receiveFilterAsync; - - GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; - dispatch_queue_t sendFilterQueue; - BOOL sendFilterAsync; - - uint32_t flags; - uint16_t config; - - uint16_t max4ReceiveSize; - uint32_t max6ReceiveSize; - - int socket4FD; - int socket6FD; - - dispatch_queue_t socketQueue; - - dispatch_source_t send4Source; - dispatch_source_t send6Source; - dispatch_source_t receive4Source; - dispatch_source_t receive6Source; - dispatch_source_t sendTimer; - - GCDAsyncUdpSendPacket *currentSend; - NSMutableArray *sendQueue; - - unsigned long socket4FDBytesAvailable; - unsigned long socket6FDBytesAvailable; - - uint32_t pendingFilterOperations; - - NSData *cachedLocalAddress4; - NSString *cachedLocalHost4; - uint16_t cachedLocalPort4; - - NSData *cachedLocalAddress6; - NSString *cachedLocalHost6; - uint16_t cachedLocalPort6; - - NSData *cachedConnectedAddress; - NSString *cachedConnectedHost; - uint16_t cachedConnectedPort; - int cachedConnectedFamily; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream4; - CFReadStreamRef readStream6; - CFWriteStreamRef writeStream4; - CFWriteStreamRef writeStream6; -#endif - - id userData; -} - -- (void)resumeSend4Source; -- (void)resumeSend6Source; -- (void)resumeReceive4Source; -- (void)resumeReceive6Source; -- (void)closeSockets; - -- (void)maybeConnect; -- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr; -- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr; - -- (void)maybeDequeueSend; -- (void)doPreSend; -- (void)doSend; -- (void)endCurrentSend; -- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout; - -- (void)doReceive; -- (void)doReceiveEOF; - -- (void)closeWithError:(NSError *)error; - -- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; - -#if TARGET_OS_IPHONE -- (BOOL)createReadAndWriteStreams:(NSError **)errPtr; -- (BOOL)registerForStreamCallbacks:(NSError **)errPtr; -- (BOOL)addStreamsToRunLoop:(NSError **)errPtr; -- (BOOL)openStreams:(NSError **)errPtr; -- (void)removeStreamsFromRunLoop; -- (void)closeReadAndWriteStreams; -#endif - -+ (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; - -#if TARGET_OS_IPHONE -// Forward declaration -+ (void)listenerThread; -#endif - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. -**/ -@interface GCDAsyncUdpSendPacket : NSObject { -@public - NSData *buffer; - NSTimeInterval timeout; - long tag; - - BOOL resolveInProgress; - BOOL filterInProgress; - - NSArray *resolvedAddresses; - NSError *resolveError; - - NSData *address; - int addressFamily; -} - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; - -@end - -@implementation GCDAsyncUdpSendPacket - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if ((self = [super init])) - { - buffer = d; - timeout = t; - tag = i; - - resolveInProgress = NO; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface GCDAsyncUdpSpecialPacket : NSObject { -@public -// uint8_t type; - - BOOL resolveInProgress; - - NSArray *addresses; - NSError *error; -} - -- (id)init; - -@end - -@implementation GCDAsyncUdpSpecialPacket - -- (id)init -{ - self = [super init]; - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation GCDAsyncUdpSocket - -- (id)init -{ - LogTrace(); - - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; -} - -- (id)initWithSocketQueue:(dispatch_queue_t)sq -{ - LogTrace(); - - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; -} - -- (id)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 -{ - LogTrace(); - - if ((self = [super init])) - { - delegate = aDelegate; - - if (dq) - { - delegateQueue = dq; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(delegateQueue); - #endif - } - - max4ReceiveSize = 9216; - max6ReceiveSize = 9216; - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(socketQueue); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL); - } - - currentSend = nil; - sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; - - #if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - #endif - } - return self; -} - -- (void)dealloc -{ - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - -#if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] removeObserver:self]; -#endif - - if (dispatch_get_current_queue() == socketQueue) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; - - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - if (dispatch_get_current_queue() == socketQueue) - { - return delegate; - } - else - { - __block id result = nil; - - dispatch_sync(socketQueue, ^{ - result = delegate; - }); - - return result; - } -} - -- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - delegate = newDelegate; - }; - - if (dispatch_get_current_queue() == socketQueue) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:NO]; -} - -- (void)synchronouslySetDelegate:(id)newDelegate -{ - [self setDelegate:newDelegate synchronously:YES]; -} - -- (dispatch_queue_t)delegateQueue -{ - if (dispatch_get_current_queue() == socketQueue) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result = NULL; - - dispatch_sync(socketQueue, ^{ - result = delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_current_queue() == socketQueue) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:NO]; -} - -- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue -{ - [self setDelegateQueue:newDelegateQueue synchronously:YES]; -} - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr -{ - if (dispatch_get_current_queue() == socketQueue) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = delegate; - dqPtr = delegateQueue; - }); - - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } -} - -- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - delegate = newDelegate; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (delegateQueue) dispatch_release(delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif - - delegateQueue = newDelegateQueue; - }; - - if (dispatch_get_current_queue() == socketQueue) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } -} - -- (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 -{ - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; -} - -- (BOOL)isIPv4Enabled -{ - // Note: YES means kIPv4Disabled is OFF - - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - result = ((config & kIPv4Disabled) == 0); - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setIPv4Enabled:(BOOL)flag -{ - // Note: YES means kIPv4Disabled is OFF - - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - - if (flag) - config &= ~kIPv4Disabled; - else - config |= kIPv4Disabled; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv6Enabled -{ - // Note: YES means kIPv6Disabled is OFF - - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - result = ((config & kIPv6Disabled) == 0); - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setIPv6Enabled:(BOOL)flag -{ - // Note: YES means kIPv6Disabled is OFF - - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - - if (flag) - config &= ~kIPv6Disabled; - else - config |= kIPv6Disabled; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4Preferred -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (config & kPreferIPv4) ? YES : NO; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv6Preferred -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (config & kPreferIPv6) ? YES : NO; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPVersionNeutral -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (config & (kPreferIPv4 | kPreferIPv6)) == 0; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setPreferIPv4 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config |= kPreferIPv4; - config &= ~kPreferIPv6; - - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setPreferIPv6 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config &= ~kPreferIPv4; - config |= kPreferIPv6; - - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setIPVersionNeutral -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config &= ~kPreferIPv4; - config &= ~kPreferIPv6; - - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint16_t)maxReceiveIPv4BufferSize -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - result = max4ReceiveSize; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max -{ - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - - max4ReceiveSize = max; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint32_t)maxReceiveIPv6BufferSize -{ - __block uint32_t result = 0; - - dispatch_block_t block = ^{ - - result = max6ReceiveSize; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max -{ - dispatch_block_t block = ^{ - - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - - max6ReceiveSize = max; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - - -- (id)userData -{ - __block id result = nil; - - dispatch_block_t block = ^{ - - result = userData; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setUserData:(id)arbitraryUserData -{ - dispatch_block_t block = ^{ - - if (userData != arbitraryUserData) - { - userData = arbitraryUserData; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Delegate Helpers -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)notifyDidConnectToAddress:(NSData *)anAddress -{ - LogTrace(); - - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) - { - id theDelegate = delegate; - NSData *address = [anAddress copy]; // In case param is NSMutableData - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didConnectToAddress:address]; - }}); - } -} - -- (void)notifyDidNotConnect:(NSError *)error -{ - LogTrace(); - - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) - { - id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didNotConnect:error]; - }}); - } -} - -- (void)notifyDidSendDataWithTag:(long)tag -{ - LogTrace(); - - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) - { - id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didSendDataWithTag:tag]; - }}); - } -} - -- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error -{ - LogTrace(); - - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) - { - id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; - }}); - } -} - -- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context -{ - LogTrace(); - - SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); - - if (delegateQueue && [delegate respondsToSelector:selector]) - { - id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; - }}); - } -} - -- (void)notifyDidCloseWithError:(NSError *)error -{ - LogTrace(); - - if (delegateQueue && [delegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) - { - id theDelegate = delegate; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocketDidClose:self withError:error]; - }}); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadConfigError - userInfo:userInfo]; -} - -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadParamError - userInfo:userInfo]; -} - -- (NSError *)gaiError:(int)gai_error -{ - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; -} - -- (NSError *)errnoErrorWithReason:(NSString *)reason -{ - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo; - - if (reason) - userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, - reason, NSLocalizedFailureReasonErrorKey, nil]; - else - userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, nil]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -- (NSError *)errnoError -{ - return [self errnoErrorWithReason:nil]; -} - -/** - * Returns a standard send timeout error. -**/ -- (NSError *)sendTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", - @"GCDAsyncUdpSocket", [NSBundle mainBundle], - @"Send operation timed out", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketSendTimeoutError - userInfo:userInfo]; -} - -- (NSError *)socketClosedError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", - @"GCDAsyncUdpSocket", [NSBundle mainBundle], - @"Socket closed", nil); - - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; -} - -- (NSError *)otherError:(NSString *)errMsg -{ - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketOtherError - userInfo:userInfo]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)preOp:(NSError **)errPtr -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to use socket 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 use socket without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -/** - * This method executes on a global concurrent queue. - * When complete, it executes the given completion block on the socketQueue. -**/ -- (void)asyncResolveHost:(NSString *)aHost - port:(uint16_t)port - withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock -{ - LogTrace(); - - // Check parameter(s) - - if (aHost == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - NSError *error = [self badParamError:msg]; - - // We should still use dispatch_async since this method is expected to be asynchronous - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - completionBlock(nil, error); - }}); - - return; - } - - // It's possible that the given aHost parameter is actually a NSMutableString. - // 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. - - NSString *host = [aHost copy]; - - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - - NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - // Wrap the native address structures and add to list - [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; - [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; - } - 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_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - 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 list - - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - else if (res->ai_family == AF_INET6) - { - // Found IPv6 address - // Wrap the native address structure and add to list - - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - completionBlock(addresses, error); - }}); - - }}); -} - -/** - * This method picks an address from the given list of addresses. - * The address picked depends upon which protocols are disabled, deactived, & preferred. - * - * Returns the address family (AF_INET or AF_INET6) of the picked address, - * or AF_UNSPEC and the corresponding error is there's a problem. -**/ -- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert([addresses count] > 0, @"Expected at least one address"); - - int resultAF = AF_UNSPEC; - NSData *resultAddress = nil; - NSError *resultError = nil; - - // Check for problems - - BOOL resolvedIPv4Address = NO; - BOOL resolvedIPv6Address = NO; - - for (NSData *address in addresses) - { - switch ([[self class] familyFromAddress:address]) - { - case AF_INET : resolvedIPv4Address = YES; break; - case AF_INET6 : resolvedIPv6Address = YES; break; - - default : NSAssert(NO, @"Addresses array contains invalid address"); - } - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - if (isIPv6Disabled && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; - BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; - - if (isIPv4Deactivated && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - if (isIPv6Deactivated && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; - } - - // Extract first IPv4 and IPv6 address in list - - BOOL ipv4WasFirstInList = YES; - NSData *address4 = nil; - NSData *address6 = nil; - - for (NSData *address in addresses) - { - int af = [[self class] familyFromAddress:address]; - - if (af == AF_INET) - { - if (address4 == nil) - { - address4 = address; - - if (address6) - break; - else - ipv4WasFirstInList = YES; - } - } - else // af == AF_INET6 - { - if (address6 == nil) - { - address6 = address; - - if (address4) - break; - else - ipv4WasFirstInList = NO; - } - } - } - - // Determine socket type - - BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - - NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); - NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); - - if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) - { - resultAF = AF_INET; - resultAddress = address4; - } - else - { - resultAF = AF_INET6; - resultAddress = address6; - } - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; -} - -/** - * Finds the address(es) of an interface description. - * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). -**/ -- (void)convertIntefaceDescription:(NSString *)interfaceDescription - port:(uint16_t)port - intoAddress4:(NSData **)interfaceAddr4Ptr - address6:(NSData **)interfaceAddr6Ptr -{ - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (interfaceDescription == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interfaceDescription isEqualToString:@"localhost"] || - [interfaceDescription isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interfaceDescription UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); - - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion; - conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); - - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 - - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; - - const char *conversion; - conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - } - - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; -} - -/** - * Converts a numeric hostname into its corresponding address. - * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) -**/ -- (void)convertNumericHost:(NSString *)numericHost - port:(uint16_t)port - intoAddress4:(NSData **)addr4Ptr - address6:(NSData **)addr6Ptr -{ - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (numericHost) - { - 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_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted - - if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0) - { - for (res = res0; res; res = res->ai_next) - { - if ((addr4 == nil) && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structure - addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if ((addr6 == nil) && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structure - addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - } - - if (addr4Ptr) *addr4Ptr = addr4; - if (addr6Ptr) *addr6Ptr = addr6; -} - -- (BOOL)isConnectedToAddress4:(NSData *)someAddr4 -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - - if (cachedConnectedFamily != AF_INET) - { - return NO; - } - - const struct sockaddr_in *sSockaddr4 = (struct sockaddr_in *)[someAddr4 bytes]; - const struct sockaddr_in *cSockaddr4 = (struct sockaddr_in *)[cachedConnectedAddress bytes]; - - if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0) - { - return NO; - } - - return YES; -} - -- (BOOL)isConnectedToAddress6:(NSData *)someAddr6 -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - - if (cachedConnectedFamily != AF_INET6) - { - return NO; - } - - const struct sockaddr_in6 *sSockaddr6 = (struct sockaddr_in6 *)[someAddr6 bytes]; - const struct sockaddr_in6 *cSockaddr6 = (struct sockaddr_in6 *)[cachedConnectedAddress bytes]; - - if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0) - { - return NO; - } - - return YES; -} - -- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 -{ - if (interfaceAddr4 == nil) - return 0; - if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) - return 0; - - int result = 0; - struct sockaddr_in *ifaceAddr = (struct sockaddr_in *)[interfaceAddr4 bytes]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET) - { - // IPv4 - - struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; - - if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - - return result; -} - -- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 -{ - if (interfaceAddr6 == nil) - return 0; - if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) - return 0; - - int result = 0; - struct sockaddr_in6 *ifaceAddr = (struct sockaddr_in6 *)[interfaceAddr6 bytes]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET6) - { - // IPv6 - - struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; - - if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } - - cursor = cursor->ifa_next; - } - - freeifaddrs(addrs); - } - - return result; -} - -- (void)setupSendAndReceiveSourcesForSocket4 -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue); - receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool { - - LogVerbose(@"send4EventBlock"); - LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); - - 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) - { - LogVerbose(@"Nothing to send"); - [self suspendSend4Source]; - } - else if (currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend4Source]; - } - else if (currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend4Source]; - } - else - { - [self doSend]; - } - - }}); - - dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool { - - LogVerbose(@"receive4EventBlock"); - - socket4FDBytesAvailable = dispatch_source_get_data(receive4Source); - LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); - - if (socket4FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; - - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - int theSocketFD = socket4FD; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theSendSource = send4Source; - dispatch_source_t theReceiveSource = receive4Source; - #endif - - dispatch_source_set_cancel_handler(send4Source, ^{ - - LogVerbose(@"send4CancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(send4Source)"); - dispatch_release(theSendSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); - - dispatch_source_set_cancel_handler(receive4Source, ^{ - - LogVerbose(@"receive4CancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(receive4Source)"); - dispatch_release(theReceiveSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); - - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. - - socket4FDBytesAvailable = 0; - flags |= kSock4CanAcceptBytes; - - flags |= kSend4SourceSuspended; - flags |= kReceive4SourceSuspended; -} - -- (void)setupSendAndReceiveSourcesForSocket6 -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue); - receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool { - - LogVerbose(@"send6EventBlock"); - LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); - - 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) - { - LogVerbose(@"Nothing to send"); - [self suspendSend6Source]; - } - else if (currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend6Source]; - } - else if (currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend6Source]; - } - else - { - [self doSend]; - } - - }}); - - dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool { - - LogVerbose(@"receive6EventBlock"); - - socket6FDBytesAvailable = dispatch_source_get_data(receive6Source); - LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); - - if (socket6FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; - - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - int theSocketFD = socket6FD; - - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_source_t theSendSource = send6Source; - dispatch_source_t theReceiveSource = receive6Source; - #endif - - dispatch_source_set_cancel_handler(send6Source, ^{ - - LogVerbose(@"send6CancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(send6Source)"); - dispatch_release(theSendSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); - - dispatch_source_set_cancel_handler(receive6Source, ^{ - - LogVerbose(@"receive6CancelBlock"); - - #if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(receive6Source)"); - dispatch_release(theReceiveSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); - - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. - - socket6FDBytesAvailable = 0; - flags |= kSock6CanAcceptBytes; - - flags |= kSend6SourceSuspended; - flags |= kReceive6SourceSuspended; -} - -- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created"); - - // CreateSocket Block - // This block will be invoked below. - - int(^createSocket)(int) = ^int (int domain) { - - int socketFD = socket(domain, SOCK_DGRAM, 0); - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int reuseaddr = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int nosigpipe = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - return socketFD; - }; - - // Create sockets depending upon given configuration. - - if (useIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = createSocket(AF_INET); - if (socket4FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - return NO; - } - } - - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = createSocket(AF_INET6); - if (socket6FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - - if (socket4FD != SOCKET_NULL) - { - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - return NO; - } - } - - // Setup send and receive sources - - if (useIPv4) - [self setupSendAndReceiveSourcesForSocket4]; - if (useIPv6) - [self setupSendAndReceiveSourcesForSocket6]; - - flags |= kDidCreateSockets; - return YES; -} - -- (BOOL)createSockets:(NSError **)errPtr -{ - LogTrace(); - - BOOL useIPv4 = [self isIPv4Enabled]; - BOOL useIPv6 = [self isIPv6Enabled]; - - return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; -} - -- (void)suspendSend4Source -{ - if (send4Source && !(flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send4Source)"); - - dispatch_suspend(send4Source); - flags |= kSend4SourceSuspended; - } -} - -- (void)suspendSend6Source -{ - if (send6Source && !(flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send6Source)"); - - dispatch_suspend(send6Source); - flags |= kSend6SourceSuspended; - } -} - -- (void)resumeSend4Source -{ - if (send4Source && (flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send4Source)"); - - dispatch_resume(send4Source); - flags &= ~kSend4SourceSuspended; - } -} - -- (void)resumeSend6Source -{ - if (send6Source && (flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send6Source)"); - - dispatch_resume(send6Source); - flags &= ~kSend6SourceSuspended; - } -} - -- (void)suspendReceive4Source -{ - if (receive4Source && !(flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive4Source)"); - - dispatch_suspend(receive4Source); - flags |= kReceive4SourceSuspended; - } -} - -- (void)suspendReceive6Source -{ - if (receive6Source && !(flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive6Source)"); - - dispatch_suspend(receive6Source); - flags |= kReceive6SourceSuspended; - } -} - -- (void)resumeReceive4Source -{ - if (receive4Source && (flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive4Source)"); - - dispatch_resume(receive4Source); - flags &= ~kReceive4SourceSuspended; - } -} - -- (void)resumeReceive6Source -{ - if (receive6Source && (flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive6Source)"); - - dispatch_resume(receive6Source); - flags &= ~kReceive6SourceSuspended; - } -} - -- (void)closeSocket4 -{ - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send4Source)"); - dispatch_source_cancel(send4Source); - - LogVerbose(@"dispatch_source_cancel(receive4Source)"); - dispatch_source_cancel(receive4Source); - - // 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. - - [self resumeSend4Source]; - [self resumeReceive4Source]; - - // The sockets will be closed by the cancel handlers of the corresponding source - - send4Source = NULL; - receive4Source = NULL; - - socket4FD = SOCKET_NULL; - - // Clear socket states - - socket4FDBytesAvailable = 0; - flags &= ~kSock4CanAcceptBytes; - - // Clear cached info - - cachedLocalAddress4 = nil; - cachedLocalHost4 = nil; - cachedLocalPort4 = 0; - } -} - -- (void)closeSocket6 -{ - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send6Source)"); - dispatch_source_cancel(send6Source); - - LogVerbose(@"dispatch_source_cancel(receive6Source)"); - dispatch_source_cancel(receive6Source); - - // 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. - - [self resumeSend6Source]; - [self resumeReceive6Source]; - - send6Source = NULL; - receive6Source = NULL; - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket6FD = SOCKET_NULL; - - // Clear socket states - - socket6FDBytesAvailable = 0; - flags &= ~kSock6CanAcceptBytes; - - // Clear cached info - - cachedLocalAddress6 = nil; - cachedLocalHost6 = nil; - cachedLocalPort6 = 0; - } -} - -- (void)closeSockets -{ - [self closeSocket4]; - [self closeSocket6]; - - flags &= ~kDidCreateSockets; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)getLocalAddress:(NSData **)dataPtr - host:(NSString **)hostPtr - port:(uint16_t *)portPtr - forSocket:(int)socketFD - withFamily:(int)socketFamily -{ - - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - - if (socketFamily == AF_INET) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - else if (socketFamily == AF_INET) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - - if (dataPtr) *dataPtr = data; - if (hostPtr) *hostPtr = host; - if (portPtr) *portPtr = port; - - return (data != nil); -} - -- (void)maybeUpdateCachedLocalAddress4Info -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) - { - return; - } - - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; - - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) - { - - cachedLocalAddress4 = address; - cachedLocalHost4 = host; - cachedLocalPort4 = port; - } -} - -- (void)maybeUpdateCachedLocalAddress6Info -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) - { - return; - } - - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; - - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) - { - - cachedLocalAddress6 = address; - cachedLocalHost6 = host; - cachedLocalPort6 = port; - } -} - -- (NSData *)localAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - if (socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalAddress4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalAddress6; - } - - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - if (socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalHost4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalHost6; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - if (socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalPort4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalPort6; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSData *)localAddress_IPv4 -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalAddress4; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost_IPv4 -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalHost4; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort_IPv4 -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress4Info]; - result = cachedLocalPort4; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSData *)localAddress_IPv6 -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalAddress6; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)localHost_IPv6 -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalHost6; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)localPort_IPv6 -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedLocalAddress6Info]; - result = cachedLocalPort6; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (void)maybeUpdateCachedConnectedAddressInfo -{ - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if (cachedConnectedAddress || (flags & kDidConnect) == 0) - { - return; - } - - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - int family = AF_UNSPEC; - - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - family = AF_INET; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - else if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - family = AF_INET6; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - - - cachedConnectedAddress = data; - cachedConnectedHost = host; - cachedConnectedPort = port; - cachedConnectedFamily = family; -} - -- (NSData *)connectedAddress -{ - __block NSData *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedAddress; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (NSString *)connectedHost -{ - __block NSString *result = nil; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedHost; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (uint16_t)connectedPort -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - [self maybeUpdateCachedConnectedAddressInfo]; - result = cachedConnectedPort; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (BOOL)isConnected -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (flags & kDidConnect) ? YES : NO; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isClosed -{ - __block BOOL result = YES; - - dispatch_block_t block = ^{ - - result = (flags & kDidCreateSockets) ? NO : YES; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv4 -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - if (flags & kDidCreateSockets) - { - result = (socket4FD != SOCKET_NULL); - } - else - { - result = [self isIPv4Enabled]; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (BOOL)isIPv6 -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - if (flags & kDidCreateSockets) - { - result = (socket6FD != SOCKET_NULL); - } - else - { - result = [self isIPv6Enabled]; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Binding -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a bind attempt. - * It is shared between the various bind methods. -**/ -- (BOOL)preBind:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if (flags & kDidBind) - { - if (errPtr) - { - NSString *msg = @"Cannot bind a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self bindToPort:port interface:nil error:errPtr]; -} - -- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preBind:&err]) - { - return_from_block; - } - - // Check the given interface - - NSData *interface4 = nil; - NSData *interface6 = nil; - - [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; - - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Determine protocol(s) - - BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); - - // Create the socket(s) if needed - - if ((flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } - - // Bind the socket(s) - - LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); - - if (useIPv4) - { - int status = bind(socket4FD, (struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - if (useIPv6) - { - int status = bind(socket6FD, (struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - // Update flags - - flags |= kDidBind; - - if (!useIPv4) flags |= kIPv4Deactivated; - if (!useIPv6) flags |= kIPv6Deactivated; - - result = YES; - - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error binding to port/interface: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preBind:&err]) - { - return_from_block; - } - - // Check the given address - - int addressFamily = [[self class] familyFromAddress:localAddr]; - - if (addressFamily == AF_UNSPEC) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; - - return_from_block; - } - - 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; - - if (isIPv4Disabled && localAddr4) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - if (isIPv6Disabled && localAddr6) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Determine protocol(s) - - BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); - - // Create the socket(s) if needed - - if ((flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } - - // Bind the socket(s) - - if (useIPv4) - { - LogVerbose(@"Binding socket to address(%@:%hu)", - [[self class] hostFromAddress:localAddr4], - [[self class] portFromAddress:localAddr4]); - - int status = bind(socket4FD, (struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - else - { - LogVerbose(@"Binding socket to address(%@:%hu)", - [[self class] hostFromAddress:localAddr6], - [[self class] portFromAddress:localAddr6]); - - int status = bind(socket6FD, (struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); - if (status == -1) - { - [self closeSockets]; - - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; - - return_from_block; - } - } - - // Update flags - - flags |= kDidBind; - - if (!useIPv4) flags |= kIPv4Deactivated; - if (!useIPv6) flags |= kIPv6Deactivated; - - result = YES; - - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error binding to address: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * This method runs through the various checks required prior to a connect attempt. - * It is shared between the various connect methods. -**/ -- (BOOL)preConnect:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot connect a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks. - - if (![self preConnect:&err]) - { - return_from_block; - } - - // Check parameter(s) - - if (host == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create the socket(s) if needed - - if ((flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // Create special connect packet - - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->resolveInProgress = YES; - - // Start asynchronous DNS resolve for host:port on background queue - - LogVerbose(@"Dispatching DNS resolve for connect..."); - - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { - - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. - - packet->resolveInProgress = NO; - - packet->addresses = addresses; - packet->error = error; - - [self maybeConnect]; - }]; - - // Updates flags, add connect packet to send queue, and pump send queue - - flags |= kConnecting; - - [sendQueue addObject:packet]; - [self maybeDequeueSend]; - - result = YES; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error connecting to host/port: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks. - - if (![self preConnect:&err]) - { - return_from_block; - } - - // Check parameter(s) - - if (remoteAddr == nil) - { - NSString *msg = @"The address param is nil. Should be a valid address."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create the socket(s) if needed - - if ((flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - // The remoteAddr parameter could be of type NSMutableData. - // So we copy it to be safe. - - NSData *address = [remoteAddr copy]; - NSArray *addresses = [NSArray arrayWithObject:address]; - - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->addresses = addresses; - - // Updates flags, add connect packet to send queue, and pump send queue - - flags |= kConnecting; - - [sendQueue addObject:packet]; - [self maybeDequeueSend]; - - result = YES; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error connecting to address: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (void)maybeConnect -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - - BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; - - if (sendQueueReady) - { - GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; - - if (connectPacket->resolveInProgress) - { - LogVerbose(@"Waiting for DNS resolve..."); - } - else - { - if (connectPacket->error) - { - [self notifyDidNotConnect:connectPacket->error]; - } - else - { - NSData *address = nil; - NSError *error = nil; - - int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; - - // Perform connect - - BOOL result = NO; - - switch (addressFamily) - { - case AF_INET : result = [self connectWithAddress4:address error:&error]; break; - case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; - } - - if (result) - { - flags |= kDidBind; - flags |= kDidConnect; - - cachedConnectedAddress = address; - cachedConnectedHost = [[self class] hostFromAddress:address]; - cachedConnectedPort = [[self class] portFromAddress:address]; - cachedConnectedFamily = addressFamily; - - [self notifyDidConnectToAddress:address]; - } - else - { - [self notifyDidNotConnect:error]; - } - } - - flags &= ~kConnecting; - - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } -} - -- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - int status = connect(socket4FD, (struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - - return NO; - } - - [self closeSocket6]; - flags |= kIPv6Deactivated; - - return YES; -} - -- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - int status = connect(socket6FD, (struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - - return NO; - } - - [self closeSocket4]; - flags |= kIPv4Deactivated; - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Multicast -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)preJoin:(NSError **)errPtr -{ - if (![self preOp:errPtr]) - { - return NO; - } - - if (!(flags & kDidBind)) - { - if (errPtr) - { - NSString *msg = @"Must bind a socket before joining a multicast group."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot join a multicast group if connected."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; -} - -- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr -{ - return [self joinMulticastGroup:group onInterface:nil error:errPtr]; -} - -- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr -{ - // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP - return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; -} - -- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr -{ - return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; -} - -- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr -{ - // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP - return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; -} - -- (BOOL)performMulticastRequest:(int)requestType - forGroup:(NSString *)group - onInterface:(NSString *)interface - error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Run through sanity checks - - if (![self preJoin:&err]) - { - return_from_block; - } - - // Convert group to address - - NSData *groupAddr4 = nil; - NSData *groupAddr6 = nil; - - [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; - - if ((groupAddr4 == nil) && (groupAddr6 == nil)) - { - NSString *msg = @"Unknown group. Specify valid group IP address."; - err = [self badParamError:msg]; - - 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) && (interfaceAddr6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Perform join - - if ((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]; - - 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)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv4 only - [self closeSocket6]; - - result = YES; - } - else if ((socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) - { - const struct sockaddr_in6 *nativeGroup = (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)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv6 only - [self closeSocket4]; - - result = YES; - } - else - { - NSString *msg = @"Socket, group, and interface do not have matching IP versions"; - err = [self badParamError:msg]; - - return_from_block; - } - - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Broadcast -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)enableBroadcast:(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 ((flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - if (socket4FD != SOCKET_NULL) - { - int value = flag ? 1 : 0; - int error = setsockopt(socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. - // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. - - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Sending -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)sendData:(NSData *)data withTag:(long)tag -{ - [self sendData:data withTimeout:-1.0 tag:tag]; -} - -- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - 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 maybeDequeueSend]; - }}); - -} - -- (void)sendData:(NSData *)data - toHost:(NSString *)host - port:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } - - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->resolveInProgress = YES; - - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { - - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. - - packet->resolveInProgress = NO; - - packet->resolvedAddresses = addresses; - packet->resolveError = error; - - if (packet == currentSend) - { - LogVerbose(@"currentSend - address resolved"); - [self doPreSend]; - } - }]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [sendQueue addObject:packet]; - [self maybeDequeueSend]; - - }}); - -} - -- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - LogTrace(); - - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } - - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; - packet->address = remoteAddr; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [sendQueue addObject:packet]; - [self maybeDequeueSend]; - }}); -} - -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue -{ - [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; -} - -- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous -{ - GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; - - if (filterBlock) - { - NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); - - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(newFilterQueue); - #endif - } - - dispatch_block_t block = ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (sendFilterQueue) dispatch_release(sendFilterQueue); - #endif - - sendFilterBlock = newFilterBlock; - sendFilterQueue = newFilterQueue; - sendFilterAsync = isAsynchronous; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)maybeDequeueSend -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - // If we don't have a send operation already in progress - if (currentSend == nil) - { - // Create the sockets if needed - if ((flags & kDidCreateSockets) == 0) - { - NSError *err = nil; - if (![self createSockets:&err]) - { - [self closeWithError:err]; - return; - } - } - - while ([sendQueue count] > 0) - { - // Dequeue the next object in the queue - currentSend = [sendQueue objectAtIndex:0]; - [sendQueue removeObjectAtIndex:0]; - - if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) - { - [self maybeConnect]; - - return; // The maybeConnect method, if it connects, will invoke this method again - } - else if (currentSend->resolveError) - { - // Notify delegate - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; - - // Clear currentSend - currentSend = nil; - - continue; - } - else - { - // Start preprocessing checks on the send packet - [self doPreSend]; - - break; - } - } - - if ((currentSend == nil) && (flags & kCloseAfterSends)) - { - [self closeWithError:nil]; - } - } -} - -/** - * This method is called after a sendPacket has been dequeued. - * It performs various preprocessing checks on the packet, - * and queries the sendFilter (if set) to determine if the packet can be sent. - * - * If the packet passes all checks, it will be passed on to the doSend method. -**/ -- (void)doPreSend -{ - LogTrace(); - - // - // 1. Check for problems with send packet - // - - BOOL waitingForResolve = NO; - NSError *error = nil; - - if (flags & kDidConnect) - { - // Connected socket - - if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) - { - NSString *msg = @"Cannot specify destination of packet for connected socket"; - error = [self badConfigError:msg]; - } - else - { - currentSend->address = cachedConnectedAddress; - currentSend->addressFamily = cachedConnectedFamily; - } - } - else - { - // Non-Connected socket - - if (currentSend->resolveInProgress) - { - // We're waiting for the packet's destination to be resolved. - waitingForResolve = YES; - } - else if (currentSend->resolveError) - { - error = currentSend->resolveError; - } - else if (currentSend->address == nil) - { - if (currentSend->resolvedAddresses == nil) - { - NSString *msg = @"You must specify destination of packet for a non-connected socket"; - error = [self badConfigError:msg]; - } - else - { - // Pick the proper address to use (out of possibly several resolved addresses) - - NSData *address = nil; - int addressFamily = AF_UNSPEC; - - addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; - - currentSend->address = address; - currentSend->addressFamily = addressFamily; - } - } - } - - if (waitingForResolve) - { - // We're waiting for the packet's destination to be resolved. - - LogVerbose(@"currentSend - waiting for address resolve"); - - if (flags & kSock4CanAcceptBytes) { - [self suspendSend4Source]; - } - if (flags & kSock6CanAcceptBytes) { - [self suspendSend6Source]; - } - - return; - } - - if (error) - { - // Unable to send packet due to some error. - // Notify delegate and move on. - - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; - [self endCurrentSend]; - [self maybeDequeueSend]; - - return; - } - - // - // 2. Query sendFilter (if applicable) - // - - if (sendFilterBlock && sendFilterQueue) - { - // Query sendFilter - - if (sendFilterAsync) - { - // Scenario 1 of 3 - Need to asynchronously query sendFilter - - currentSend->filterInProgress = YES; - GCDAsyncUdpSendPacket *sendPacket = currentSend; - - dispatch_async(sendFilterQueue, ^{ @autoreleasepool { - - BOOL allowed = sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - sendPacket->filterInProgress = NO; - if (sendPacket == currentSend) - { - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - }}); - }}); - } - else - { - // Scenario 2 of 3 - Need to synchronously query sendFilter - - __block BOOL allowed = YES; - - dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { - - allowed = sendFilterBlock(currentSend->buffer, currentSend->address, currentSend->tag); - }}); - - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - } - else // if (!sendFilterBlock || !sendFilterQueue) - { - // Scenario 3 of 3 - No sendFilter. Just go straight into sending. - - [self doSend]; - } -} - -/** - * This method performs the actual sending of data in the currentSend packet. - * It should only be called if the -**/ -- (void)doSend -{ - LogTrace(); - - NSAssert(currentSend != nil, @"Invalid logic"); - - // Perform the actual send - - ssize_t result = 0; - - if (flags & kDidConnect) - { - // Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - if (currentSend->addressFamily == AF_INET) - { - result = send(socket4FD, buffer, length, 0); - LogVerbose(@"send(socket4FD) = %d", result); - } - else - { - result = send(socket6FD, buffer, length, 0); - LogVerbose(@"send(socket6FD) = %d", result); - } - } - else - { - // Non-Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - const void *dst = [currentSend->address bytes]; - socklen_t dstSize = (socklen_t)[currentSend->address length]; - - if (currentSend->addressFamily == AF_INET) - { - result = sendto(socket4FD, buffer, length, 0, dst, dstSize); - LogVerbose(@"sendto(socket4FD) = %d", result); - } - else - { - result = sendto(socket6FD, buffer, length, 0, dst, dstSize); - LogVerbose(@"sendto(socket6FD) = %d", result); - } - } - - // If the socket wasn't bound before, it is now - - if ((flags & kDidBind) == 0) - { - flags |= kDidBind; - } - - // Check the results. - // - // From the send() & sendto() manpage: - // - // Upon successful completion, the number of bytes which were sent is returned. - // Otherwise, -1 is returned and the global variable errno is set to indicate the error. - - BOOL waitingForSocket = NO; - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in send() function."]; - } - - if (waitingForSocket) - { - // Not enough room in the underlying OS socket send buffer. - // Wait for a notification of available space. - - LogVerbose(@"currentSend - waiting for socket"); - - if (!(flags & kSock4CanAcceptBytes)) { - [self resumeSend4Source]; - } - if (!(flags & kSock6CanAcceptBytes)) { - [self resumeSend6Source]; - } - - if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) - { - // Unable to send packet right away. - // Start timer to timeout the send operation. - - [self setupSendTimerWithTimeout:currentSend->timeout]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else // done - { - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } -} - -/** - * Releases all resources associated with the currentSend. -**/ -- (void)endCurrentSend -{ - if (sendTimer) - { - dispatch_source_cancel(sendTimer); - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_release(sendTimer); - #endif - sendTimer = NULL; - } - - currentSend = nil; -} - -/** - * Performs the operations to timeout the current send operation, and move on. -**/ -- (void)doSendTimeout -{ - LogTrace(); - - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; - [self endCurrentSend]; - [self maybeDequeueSend]; -} - -/** - * Sets up a timer that fires to timeout the current send operation. - * This method should only be called once per send packet. -**/ -- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout -{ - NSAssert(sendTimer == NULL, @"Invalid logic"); - NSAssert(timeout >= 0.0, @"Invalid logic"); - - LogTrace(); - - sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); - - dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { - - [self doSendTimeout]; - }}); - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); - - dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(sendTimer); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Receiving -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)receiveOnce:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ - - if ((flags & kReceiveOnce) == 0) - { - if ((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."; - - err = [self badConfigError:msg]; - return_from_block; - } - - flags |= kReceiveOnce; // Enable - flags &= ~kReceiveContinuous; // Disable - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doReceive]; - }}); - } - - result = YES; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error in beginReceiving: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (BOOL)beginReceiving:(NSError **)errPtr -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ - - if ((flags & kReceiveContinuous) == 0) - { - if ((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."; - - err = [self badConfigError:msg]; - return_from_block; - } - - flags |= kReceiveContinuous; // Enable - flags &= ~kReceiveOnce; // Disable - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self doReceive]; - }}); - } - - result = YES; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); - - if (err) - LogError(@"Error in beginReceiving: %@", err); - - if (errPtr) - *errPtr = err; - - return result; -} - -- (void)pauseReceiving -{ - LogTrace(); - - dispatch_block_t block = ^{ - - flags &= ~kReceiveOnce; // Disable - flags &= ~kReceiveContinuous; // Disable - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue -{ - [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; -} - -- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock - withQueue:(dispatch_queue_t)filterQueue - isAsynchronous:(BOOL)isAsynchronous -{ - GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; - - if (filterBlock) - { - NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); - - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; - #if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_retain(newFilterQueue); - #endif - } - - dispatch_block_t block = ^{ - - #if NEEDS_DISPATCH_RETAIN_RELEASE - if (receiveFilterQueue) dispatch_release(receiveFilterQueue); - #endif - - receiveFilterBlock = newFilterBlock; - receiveFilterQueue = newFilterQueue; - receiveFilterAsync = isAsynchronous; - }; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)doReceive -{ - LogTrace(); - - if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) - { - LogVerbose(@"Receiving is paused..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) - { - LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) - { - LogVerbose(@"No data available to receive..."); - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - - return; - } - - // Figure out if we should receive on socket4 or socket6 - - BOOL doReceive4; - - if (flags & kDidConnect) - { - // Connected socket - - doReceive4 = (socket4FD != SOCKET_NULL); - } - else - { - // Non-Connected socket - - if (socket4FDBytesAvailable > 0) - { - if (socket6FDBytesAvailable > 0) - { - // Bytes available on socket4 & socket6 - - doReceive4 = (flags & kFlipFlop) ? YES : NO; - - flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) - } - else { - // Bytes available on socket4, but not socket6 - doReceive4 = YES; - } - } - else { - // Bytes available on socket6, but not socket4 - doReceive4 = NO; - } - } - - // Perform socket IO - - ssize_t result = 0; - - NSData *data = nil; - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (doReceive4) - { - NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv4"); - - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - size_t bufSize = MIN(max4ReceiveSize, socket4FDBytesAvailable); - void *buf = malloc(bufSize); - - result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); - LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket4FDBytesAvailable) - socket4FDBytesAvailable = 0; - else - socket4FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - } - else - { - LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); - socket4FDBytesAvailable = 0; - free(buf); - } - } - else - { - NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv6"); - - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - size_t bufSize = MIN(max6ReceiveSize, socket6FDBytesAvailable); - void *buf = malloc(bufSize); - - result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); - LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket6FDBytesAvailable) - socket6FDBytesAvailable = 0; - else - socket6FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - } - else - { - LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); - socket6FDBytesAvailable = 0; - free(buf); - } - } - - - BOOL waitingForSocket = NO; - BOOL notifiedDelegate = NO; - BOOL ignored = NO; - - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; - } - else - { - if (flags & kDidConnect) - { - if (addr4 && ![self isConnectedToAddress4:addr4]) - ignored = YES; - if (addr6 && ![self isConnectedToAddress6:addr6]) - ignored = YES; - } - - NSData *addr = (addr4 != nil) ? addr4 : addr6; - - if (!ignored) - { - if (receiveFilterBlock && receiveFilterQueue) - { - // Run data through filter, and if approved, notify delegate - - __block id filterContext = nil; - __block BOOL allowed = NO; - - if (receiveFilterAsync) - { - pendingFilterOperations++; - dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { - - allowed = receiveFilterBlock(data, addr, &filterContext); - - // Transition back to socketQueue to get the current delegate / delegateQueue - dispatch_async(socketQueue, ^{ @autoreleasepool { - - pendingFilterOperations--; - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - } - - if (flags & kReceiveOnce) - { - if (allowed) - { - // The delegate has been notified, - // so our receive once operation has completed. - flags &= ~kReceiveOnce; - } - else if (pendingFilterOperations == 0) - { - // All pending filter operations have completed, - // and none were allowed through. - // Our receive once operation hasn't completed yet. - [self doReceive]; - } - } - }}); - }}); - } - else // if (!receiveFilterAsync) - { - dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { - - allowed = receiveFilterBlock(data, addr, &filterContext); - }}); - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - notifiedDelegate = YES; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - ignored = YES; - } - } - } - else // if (!receiveFilterBlock || !receiveFilterQueue) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; - notifiedDelegate = YES; - } - } - } - - if (waitingForSocket) - { - // Wait for a notification of available data. - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else - { - if (flags & kReceiveContinuous) - { - // Continuous receive mode - [self doReceive]; - } - else - { - // One-at-a-time receive mode - if (notifiedDelegate) - { - // The delegate has been notified (no set filter). - // So our receive once operation has completed. - flags &= ~kReceiveOnce; - } - else if (ignored) - { - [self doReceive]; - } - else - { - // Waiting on asynchronous receive filter... - } - } - } -} - -- (void)doReceiveEOF -{ - LogTrace(); - - [self closeWithError:[self socketClosedError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Closing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)closeWithError:(NSError *)error -{ - LogVerbose(@"closeWithError: %@", error); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if (currentSend) [self endCurrentSend]; - - [sendQueue removeAllObjects]; - - // If a socket has been created, we should notify the delegate. - BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; - - // Close all sockets, send/receive sources, cfstreams, etc -#if TARGET_OS_IPHONE - [self removeStreamsFromRunLoop]; - [self closeReadAndWriteStreams]; -#endif - [self closeSockets]; - - // Clear all flags (config remains as is) - flags = 0; - - if (shouldCallDelegate) - { - [self notifyDidCloseWithError:error]; - } -} - -- (void)close -{ - LogTrace(); - - dispatch_block_t block = ^{ @autoreleasepool { - - [self closeWithError:nil]; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (void)closeAfterSending -{ - LogTrace(); - - dispatch_block_t block = ^{ @autoreleasepool { - - flags |= kCloseAfterSends; - - if (currentSend == nil && [sendQueue count] == 0) - { - [self closeWithError:nil]; - } - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -static NSThread *listenerThread; - -+ (void)ignore:(id)_ -{} - -+ (void)startListenerThreadIfNeeded -{ - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - listenerThread = [[NSThread alloc] initWithTarget:self - selector:@selector(listenerThread) - object:nil]; - [listenerThread start]; - }); -} - -+ (void)listenerThread -{ - @autoreleasepool { - - [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; - - LogInfo(@"ListenerThread: Started"); - - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for a decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; - - [[NSRunLoop currentRunLoop] run]; - - LogInfo(@"ListenerThread: Stopped"); - } -} - -+ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncUdpSocket->readStream4) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->readStream6) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream4) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream6) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); -} - -+ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket -{ - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); - - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - - if (asyncUdpSocket->readStream4) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->readStream6) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream4) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); - - if (asyncUdpSocket->writeStream6) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); -} - -static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo) -{ - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; - - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFReadStreamCallback - Open"); - break; - } - case kCFStreamEventHasBytesAvailable: - { - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->readStream4 && - stream != asyncUdpSocket->readStream6 ) - { - LogCVerbose(@"CFReadStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); - } - } - } -} - -static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; - - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFWriteStreamCallback - Open"); - break; - } - case kCFStreamEventCanAcceptBytes: - { - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->writeStream4 && - stream != asyncUdpSocket->writeStream6 ) - { - LogCVerbose(@"CFWriteStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); - } - } - } -} - -- (BOOL)createReadAndWriteStreams:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - NSError *err = nil; - - if (readStream4 || writeStream4 || readStream6 || writeStream6) - { - // Streams already created - return YES; - } - - if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) - { - err = [self otherError:@"Cannot create streams without a file descriptor"]; - goto Failed; - } - - // Create streams - - LogVerbose(@"Creating read and write stream(s)..."); - - if (socket4FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4); - if (!readStream4 || !writeStream4) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6); - if (!readStream6 || !writeStream6) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; - goto Failed; - } - } - - // Ensure the CFStream's don't close our underlying socket - - CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - - CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - - return YES; - -Failed: - if (readStream4) - { - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } - - if (errPtr) - *errPtr = err; - - return NO; -} - -- (BOOL)registerForStreamCallbacks:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); - - NSError *err = nil; - - streamContext.version = 0; - streamContext.info = (__bridge void *)self; - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - -// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); -// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); - - if (socket4FD != SOCKET_NULL) - { - if (readStream4 == NULL || writeStream4 == NULL) - { - err = [self otherError:@"Read/Write stream4 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - if (readStream6 == NULL || writeStream6 == NULL) - { - err = [self otherError:@"Read/Write stream6 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; - goto Failed; - } - } - - return YES; - -Failed: - if (readStream4) { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - } - if (writeStream4) { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - } - if (readStream6) { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - } - if (writeStream6) { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - } - - if (errPtr) *errPtr = err; - return NO; -} - -- (BOOL)addStreamsToRunLoop:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); - - if (!(flags & kAddedStreamListener)) - { - [[self class] startListenerThreadIfNeeded]; - [[self class] performSelector:@selector(addStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; - - flags |= kAddedStreamListener; - } - - return YES; -} - -- (BOOL)openStreams:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); - - NSError *err = nil; - - if (socket4FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream4); - BOOL r2 = CFWriteStreamOpen(writeStream4); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream6); - BOOL r2 = CFWriteStreamOpen(writeStream6); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; - goto Failed; - } - } - - return YES; - -Failed: - if (errPtr) *errPtr = err; - return NO; -} - -- (void)removeStreamsFromRunLoop -{ - LogTrace(); - NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue"); - - if (flags & kAddedStreamListener) - { - [[self class] performSelector:@selector(removeStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; - - flags &= ~kAddedStreamListener; - } -} - -- (void)closeReadAndWriteStreams -{ - LogTrace(); - - if (readStream4) - { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } -} - -#endif - -- (void)applicationWillEnterForeground:(NSNotification *)notification -{ - LogTrace(); - - // If the application was backgrounded, then iOS may have shut down our sockets. - // So we take a quick look to see if any of them received an EOF. - - dispatch_block_t block = ^{ @autoreleasepool { - - [self resumeReceive4Source]; - [self resumeReceive6Source]; - }}; - - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_current_queue() == socketQueue) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (int)socketFD -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; -} - -- (int)socket4FD -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - return socket4FD; -} - -- (int)socket6FD -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } - - return socket6FD; -} - -#if TARGET_OS_IPHONE - -- (CFReadStreamRef)readStream -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } - - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } - - // Todo... - - if (readStream4) - return readStream4; - else - return readStream6; -} - -- (CFWriteStreamRef)writeStream -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } - - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } - - if (writeStream4) - return writeStream4; - else - return writeStream6; -} - -- (BOOL)enableBackgroundingOnSockets -{ - if (dispatch_get_current_queue() != socketQueue) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NO; - } - - // Why is this commented out? - // See comments below. - -// NSError *err = nil; -// if (![self createReadAndWriteStreams:&err]) -// { -// LogError(@"Error creating CFStream(s): %@", err); -// return NO; -// } -// -// LogVerbose(@"Enabling backgrouding on socket"); -// -// BOOL r1, r2; -// -// if (readStream4 && writeStream4) -// { -// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// -// if (!r1 || !r2) -// { -// LogError(@"Error setting voip type (IPv4)"); -// return NO; -// } -// } -// -// if (readStream6 && writeStream6) -// { -// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// -// if (!r1 || !r2) -// { -// LogError(@"Error setting voip type (IPv6)"); -// return NO; -// } -// } -// -// return YES; - - // The above code will actually appear to work. - // The methods will return YES, and everything will appear fine. - // - // One tiny problem: the sockets will still get closed when the app gets backgrounded. - // - // Apple does not officially support backgrounding UDP sockets. - - return NO; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -+ (NSString *)hostFromAddress:(NSData *)address -{ - NSString *host = nil; - [self getHost:&host port:NULL family:NULL fromAddress:address]; - - return host; -} - -+ (uint16_t)portFromAddress:(NSData *)address -{ - uint16_t port = 0; - [self getHost:NULL port:&port family:NULL fromAddress:address]; - - return port; -} - -+ (int)familyFromAddress:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return af; -} - -+ (BOOL)isIPv4Address:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return (af == AF_INET); -} - -+ (BOOL)isIPv6Address:(NSData *)address -{ - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; - - return (af == AF_INET6); -} - -+ (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:(int *)afPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; - - if (addrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; - if (afPtr) *afPtr = AF_INET; - - return YES; - } - } - else if (addrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; - if (afPtr) *afPtr = AF_INET6; - - return YES; - } - } - } - - if (hostPtr) *hostPtr = nil; - if (portPtr) *portPtr = 0; - if (afPtr) *afPtr = AF_UNSPEC; - - return NO; -} - -@end diff --git a/Pods/CocoaAsyncSocket/README.markdown b/Pods/CocoaAsyncSocket/README.markdown deleted file mode 100644 index ef37150..0000000 --- a/Pods/CocoaAsyncSocket/README.markdown +++ /dev/null @@ -1,70 +0,0 @@ -CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for Mac and iOS. The classes are described below. - -## TCP - -**GCDAsyncSocket** and **AsyncSocket** are TCP/IP socket networking libraries. Here are the key features available in both: - -- Native objective-c, fully self-contained in one class.
- _No need to muck around with sockets or streams. This class handles everything for you._ - -- Full delegate support
- _Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._ - -- Queued non-blocking reads and writes, with optional timeouts.
- _You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._ - -- Automatic socket acceptance.
- _Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._ - -- Support for TCP streams over IPv4 and IPv6.
- _Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._ - -- Support for TLS / SSL
- _Secure your socket with ease using just a single method call. Available for both client and server sockets._ - -**GCDAsyncSocket** is built atop Grand Central Dispatch: - -- Fully GCD based and Thread-Safe
- _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._ - -- The Latest Technology & Performance Optimizations
- _Internally the library takes advantage of technologies such as [kqueue's](http://en.wikipedia.org/wiki/Kqueue) to limit [system calls](http://en.wikipedia.org/wiki/System_call) and optimize buffer allocations. In other words, peak performance._ - -**AsyncSocket** wraps CFSocket and CFStream: - -- Fully Run-loop based
- _Use it on the main thread or a worker thread. It plugs into the NSRunLoop with configurable modes._ - -## UDP - -**GCDAsyncUdpSocket** and **AsyncUdpSocket** are UDP/IP socket networking libraries. Here are the key features available in both: - -- Native objective-c, fully self-contained in one class.
- _No need to muck around with low-level sockets. This class handles everything for you._ - -- Full delegate support.
- _Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._ - -- Queued non-blocking send and receive operations, with optional timeouts.
- _You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._ - -- Support for IPv4 and IPv6.
- _Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._ - -**GCDAsyncUdpSocket** is built atop Grand Central Dispatch: - -- Fully GCD based and Thread-Safe
- _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._ - -**AsyncUdpSocket** wraps CFSocket: - -- Fully Run-loop based
- _Use it on the main thread or a worker thread. It plugs into the NSRunLoop with configurable modes._ - -*** - -Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoaasyncsocket)**. -
-
-Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2) - diff --git a/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.h b/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.h deleted file mode 100644 index 33c1a7f..0000000 --- a/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.h +++ /dev/null @@ -1,659 +0,0 @@ -// -// AsyncSocket.h -// -// This class is in the public domain. -// Originally created by Dustin Voss on Wed Jan 29 2003. -// Updated and maintained by Deusty Designs and the Mac development community. -// -// http://code.google.com/p/cocoaasyncsocket/ -// - -#import - -@class AsyncSocket; -@class AsyncReadPacket; -@class AsyncWritePacket; - -extern NSString *const AsyncSocketException; -extern NSString *const AsyncSocketErrorDomain; - -enum AsyncSocketError -{ - AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. - AsyncSocketNoError = 0, // Never used. - AsyncSocketCanceledError, // onSocketWillConnect: returned NO. - AsyncSocketConnectTimeoutError, - AsyncSocketReadMaxedOutError, // Reached set maxLength without completing - AsyncSocketReadTimeoutError, - AsyncSocketWriteTimeoutError -}; -typedef enum AsyncSocketError AsyncSocketError; - -@protocol AsyncSocketDelegate -@optional - -/** - * In the event of an error, the socket is closed. - * You may call "unreadData" during this call-back to get the last bit of data off the socket. - * When connecting, this delegate method may be called - * before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". -**/ -- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; - -/** - * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, - * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". - * - * If you call the disconnect method, and the socket wasn't already disconnected, - * this delegate method will be called before the disconnect method returns. -**/ -- (void)onSocketDidDisconnect:(AsyncSocket *)sock; - -/** - * Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have - * the same delegate and will call "onSocket:didConnectToHost:port:". -**/ -- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; - -/** - * Called when a new socket is spawned to handle a connection. This method should return the run-loop of the - * thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. -**/ -- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; - -/** - * Called when a socket is about to connect. This method should return YES to continue, or NO to abort. - * If aborted, will result in AsyncSocketCanceledError. - * - * If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the - * CFReadStream and CFWriteStream as desired prior to connection. - * - * If the connectToAddress:error: method was called, the delegate will be able to access and configure the - * CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and - * configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. -**/ -- (BOOL)onSocketWillConnect:(AsyncSocket *)sock; - -/** - * 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)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. -**/ -- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; - -/** - * 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. -**/ -- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called when a socket has completed writing the requested data. Not called if there is an error. -**/ -- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; - -/** - * 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. -**/ -- (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; - -/** - * Called if a read operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been read so far for the read operation. - * - * Note that this method may be called multiple times for a single read if you return positive numbers. -**/ -- (NSTimeInterval)onSocket:(AsyncSocket *)sock - shouldTimeoutReadWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called if a write operation has reached its timeout without completing. - * This method allows you to optionally extend the timeout. - * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. - * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. - * - * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. - * The length parameter is the number of bytes that have been written so far for the write operation. - * - * Note that this method may be called multiple times for a single write if you return positive numbers. -**/ -- (NSTimeInterval)onSocket:(AsyncSocket *)sock - shouldTimeoutWriteWithTag:(long)tag - elapsed:(NSTimeInterval)elapsed - bytesDone:(NSUInteger)length; - -/** - * Called after the socket has successfully completed SSL/TLS negotiation. - * This method is not called unless you use the provided startTLS method. - * - * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, - * and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. -**/ -- (void)onSocketDidSecure:(AsyncSocket *)sock; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@interface AsyncSocket : NSObject -{ - CFSocketNativeHandle theNativeSocket4; - CFSocketNativeHandle theNativeSocket6; - - CFSocketRef theSocket4; // IPv4 accept or connect socket - CFSocketRef theSocket6; // IPv6 accept or connect socket - - CFReadStreamRef theReadStream; - CFWriteStreamRef theWriteStream; - - CFRunLoopSourceRef theSource4; // For theSocket4 - CFRunLoopSourceRef theSource6; // For theSocket6 - CFRunLoopRef theRunLoop; - CFSocketContext theContext; - NSArray *theRunLoopModes; - - NSTimer *theConnectTimer; - - NSMutableArray *theReadQueue; - AsyncReadPacket *theCurrentRead; - NSTimer *theReadTimer; - NSMutableData *partialReadBuffer; - - NSMutableArray *theWriteQueue; - AsyncWritePacket *theCurrentWrite; - NSTimer *theWriteTimer; - - id theDelegate; - UInt16 theFlags; - - long theUserData; -} - -- (id)init; -- (id)initWithDelegate:(id)delegate; -- (id)initWithDelegate:(id)delegate userData:(long)userData; - -/* String representation is long but has no "\n". */ -- (NSString *)description; - -/** - * Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate - * before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. -**/ -- (id)delegate; -- (BOOL)canSafelySetDelegate; -- (void)setDelegate:(id)delegate; - -/* User data can be a long, or an id or void * cast to a long. */ -- (long)userData; -- (void)setUserData:(long)userData; - -/* Don't use these to read or write. And don't close them either! */ -- (CFSocketRef)getCFSocket; -- (CFReadStreamRef)getCFReadStream; -- (CFWriteStreamRef)getCFWriteStream; - -// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in -// and the other accept/connect methods can't be called without disconnecting the socket first. -// If the attempt fails or times out, these methods either return NO or -// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". - -// When an incoming connection is accepted, AsyncSocket invokes several delegate methods. -// These methods are (in chronological order): -// 1. onSocket:didAcceptNewSocket: -// 2. onSocket:wantsRunLoopForNewSocket: -// 3. onSocketWillConnect: -// -// Your server code will need to retain the accepted socket (if you want to accept it). -// The best place to do this is probably in the onSocket:didAcceptNewSocket: method. -// -// After the read and write streams have been setup for the newly accepted socket, -// the onSocket:didConnectToHost:port: method will be called on the proper run loop. -// -// Multithreading Note: If you're going to be moving the newly accepted socket to another run -// loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the -// onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. -// Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. - -/** - * Tells the socket to begin listening and accepting connections on the given port. - * When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) -**/ -- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; - -/** - * This method is the same as acceptOnPort:error: with the additional option - * of specifying which interface to listen on. So, for example, if you were writing code for a server that - * has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it - * to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. - * You may also use the special strings "localhost" or "loopback" to specify that - * the socket only accept connections from the local machine. - * - * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. -**/ -- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; - -/** - * Connects to the given host and port. - * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") -**/ -- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; - -/** - * This method is the same as connectToHost:onPort:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. -**/ -- (BOOL)connectToHost:(NSString *)hostname - onPort:(UInt16)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. - * For example, a NSData object returned from NSNetService's addresses method. - * - * If you have an existing struct sockaddr you can convert it to a NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * This method is the same as connectToAddress:error: with an additional timeout option. - * To not time out use a negative time interval, or simply use the connectToAddress:error: method. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; - -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterfaceAddress:(NSData *)interfaceAddr - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr; - -/** - * Disconnects immediately. Any pending reads or writes are dropped. - * If the socket is not already disconnected, the onSocketDidDisconnect delegate method - * will be called immediately, before this method returns. - * - * Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) - * [asyncSocket setDelegate:nil]; - * [asyncSocket disconnect]; - * [asyncSocket release]; -**/ -- (void)disconnect; - -/** - * Disconnects after all pending reads have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending writes. -**/ -- (void)disconnectAfterReading; - -/** - * Disconnects after all pending writes have completed. - * After calling this, the read and write methods will do nothing. - * The socket will disconnect even if there are still pending reads. -**/ -- (void)disconnectAfterWriting; - -/** - * Disconnects after all pending reads and writes have completed. - * After calling this, the read and write methods will do nothing. -**/ -- (void)disconnectAfterReadingAndWriting; - -/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ -- (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)connectedPort; - -- (NSString *)localHost; -- (UInt16)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. -**/ -- (NSData *)connectedAddress; -- (NSData *)localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. -**/ -- (BOOL)isIPv4; -- (BOOL)isIPv6; - -// The readData and writeData methods won't block (they are asynchronous). -// -// When a read is complete the onSocket:didReadData:withTag: delegate method is called. -// When a write is complete the onSocket:didWriteDataWithTag: delegate method is called. -// -// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) -// If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method -// is called to optionally allow you to extend the timeout. -// Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". -// -// The tag is for your convenience. -// You can use it as an array index, step number, state id, pointer, etc. - -/** - * Reads the first available bytes that become available on the socket. - * - * If the timeout value is negative, the read operation will not use a timeout. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads the first available bytes that become available on the socket. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * 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 maxLength is zero, no length restriction is enforced. - * - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer. -**/ -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Reads the given number of bytes. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If the length is 0, this method does nothing and the delegate is not called. -**/ -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads the given number of bytes. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 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, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer. -**/ -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing, and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for - * a character, the read will prematurely end. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * 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 bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for - * a character, the read will prematurely end. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * - * If the timeout value is negative, the read operation will not use a timeout. - * - * If maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass nil or zero-length data as the "data" parameter, - * the method will do nothing, and the delegate will not be called. - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing, and the delegate will not be called. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for - * a character, the read will prematurely end. -**/ -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; - -/** - * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. - * The bytes will be appended to the given byte buffer starting at the given offset. - * The given buffer will automatically be increased in size if needed. - * 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 maxLength is zero, no length restriction is enforced. - * Otherwise if maxLength bytes are read without completing the read, - * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. - * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. - * - * If you pass a maxLength parameter that is less than the length of the data parameter, - * the method will do nothing, and the delegate will not be called. - * If the bufferOffset is greater than the length of the given buffer, - * the method will do nothing, and the delegate will not be called. - * - * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. - * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. - * That is, it will reference the bytes that were appended to the given buffer. - * - * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. - * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for - * a character, the read will prematurely end. -**/ -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag; - -/** - * Writes data to the socket, and calls the delegate when finished. - * - * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. - * If the timeout value is negative, the write operation will not use a timeout. -**/ -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). - * "tag", "done" and "total" will be filled in if they aren't NULL. -**/ -- (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; -- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; - -/** - * Secures the connection using SSL/TLS. - * - * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes - * 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 possible keys and values for the TLS settings are well documented. - * Some possible keys are: - * - kCFStreamSSLLevel - * - kCFStreamSSLAllowsExpiredCertificates - * - kCFStreamSSLAllowsExpiredRoots - * - kCFStreamSSLAllowsAnyRoot - * - kCFStreamSSLValidatesCertificateChain - * - kCFStreamSSLPeerName - * - kCFStreamSSLCertificates - * - kCFStreamSSLIsServer - * - * Please refer to Apple's documentation for associated values, as well as other possible keys. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * 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 - * give it a name to verify against via the kCFStreamSSLPeerName key. - * The security implications of this are important to understand. - * Imagine you are attempting to create a secure connection to MySecureServer.com, - * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. - * If you simply use the default settings, and MaliciousServer.com has a valid certificate, - * 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; - -/** - * For handling readDataToData requests, data is necessarily read from the socket in small increments. - * The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and - * store any overflow in a small internal buffer. - * This is termed pre-buffering, as some data may be read for you before you ask for it. - * If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. - * - * The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. - * It is highly recommended one leave this set to YES. - * - * This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. - * In that case, this method exists to allow one to easily enable pre-buffering when ready. -**/ -- (void)enablePreBuffering; - -/** - * When you create an AsyncSocket, it is added to the runloop of the current thread. - * So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. - * - * If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to - * allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. - * - * If, however, you need to move the socket to a separate thread at a later time, this - * method may be used to accomplish the task. - * - * This method must be called from the thread/runloop the socket is currently running on. - * - * Note: After calling this method, all further method calls to this object should be done from the given runloop. - * Also, all delegate calls will be sent on the given runloop. -**/ -- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; - -/** - * Allows you to configure which run loop modes the socket uses. - * The default set of run loop modes is NSDefaultRunLoopMode. - * - * If you'd like your socket to continue operation during other modes, you may want to add modes such as - * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. - * - * Accepted sockets will automatically inherit the same run loop modes as the listening socket. - * - * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. -**/ -- (BOOL)setRunLoopModes:(NSArray *)runLoopModes; -- (BOOL)addRunLoopMode:(NSString *)runLoopMode; -- (BOOL)removeRunLoopMode:(NSString *)runLoopMode; - -/** - * Returns the current run loop modes the AsyncSocket instance is operating in. - * The default set of run loop modes is NSDefaultRunLoopMode. -**/ -- (NSArray *)runLoopModes; - -/** - * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read - * any data that's left on the socket. -**/ -- (NSData *)unreadData; - -/* A few common line separators, for use with the readDataToData:... methods. */ -+ (NSData *)CRLFData; // 0x0D0A -+ (NSData *)CRData; // 0x0D -+ (NSData *)LFData; // 0x0A -+ (NSData *)ZeroData; // 0x00 - -@end diff --git a/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.m b/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.m deleted file mode 100644 index e946c91..0000000 --- a/Pods/CocoaAsyncSocket/RunLoop/AsyncSocket.m +++ /dev/null @@ -1,4312 +0,0 @@ -// -// AsyncSocket.m -// -// This class is in the public domain. -// Originally created by Dustin Voss on Wed Jan 29 2003. -// Updated and maintained by Deusty Designs and the Mac development community. -// -// http://code.google.com/p/cocoaasyncsocket/ -// - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#import "AsyncSocket.h" -#import -#import -#import -#import - -#if TARGET_OS_IPHONE -// Note: You may need to add the CFNetwork Framework to your project -#import -#endif - -#pragma mark Declarations - -#define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default - -#define READQUEUE_CAPACITY 5 // Initial capacity -#define WRITEQUEUE_CAPACITY 5 // Initial capacity -#define READALL_CHUNKSIZE 256 // Incremental increase in buffer size -#define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass - -// AsyncSocket is RunLoop based, and is thus not thread-safe. -// You must always access your AsyncSocket instance from the thread/runloop in which the instance is running. -// You can use methods such as performSelectorOnThread to accomplish this. -// Failure to comply with these thread-safety rules may result in errors. -// You can enable this option to help diagnose where you are incorrectly accessing your socket. -#if DEBUG - #define DEBUG_THREAD_SAFETY 1 -#else - #define DEBUG_THREAD_SAFETY 0 -#endif -// -// If you constantly need to access your socket from multiple threads -// then you may consider using GCDAsyncSocket instead, which is thread-safe. - -NSString *const AsyncSocketException = @"AsyncSocketException"; -NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; - - -enum AsyncSocketFlags -{ - kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled - kDidStartDelegate = 1 << 1, // If set, disconnection results in delegate call - kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream - kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream - kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete - kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed - kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued - kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error - kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled - kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled - kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown. - kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown. -}; - -@interface AsyncSocket (Private) - -// Connecting -- (void)startConnectTimeout:(NSTimeInterval)timeout; -- (void)endConnectTimeout; -- (void)doConnectTimeout:(NSTimer *)timer; - -// Socket Implementation -- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; -- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr; -- (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr; -- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; -- (BOOL)configureSocketAndReturnError:(NSError **)errPtr; -- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; -- (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket; -- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err; - -// Stream Implementation -- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; -- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; -- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; -- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr; -- (BOOL)openStreamsAndReturnError:(NSError **)errPtr; -- (void)doStreamOpen; -- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr; - -// Disconnect Implementation -- (void)closeWithError:(NSError *)err; -- (void)recoverUnreadData; -- (void)emptyQueues; -- (void)close; - -// Errors -- (NSError *)getErrnoError; -- (NSError *)getAbortError; -- (NSError *)getStreamError; -- (NSError *)getSocketError; -- (NSError *)getConnectTimeoutError; -- (NSError *)getReadMaxedOutError; -- (NSError *)getReadTimeoutError; -- (NSError *)getWriteTimeoutError; -- (NSError *)errorFromCFStreamError:(CFStreamError)err; - -// Diagnostics -- (BOOL)isDisconnected; -- (BOOL)areStreamsConnected; -- (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; -- (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; -- (NSString *)connectedHostFromCFSocket4:(CFSocketRef)socket; -- (NSString *)connectedHostFromCFSocket6:(CFSocketRef)socket; -- (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; -- (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; -- (UInt16)connectedPortFromCFSocket4:(CFSocketRef)socket; -- (UInt16)connectedPortFromCFSocket6:(CFSocketRef)socket; -- (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; -- (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; -- (NSString *)localHostFromCFSocket4:(CFSocketRef)socket; -- (NSString *)localHostFromCFSocket6:(CFSocketRef)socket; -- (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; -- (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; -- (UInt16)localPortFromCFSocket4:(CFSocketRef)socket; -- (UInt16)localPortFromCFSocket6:(CFSocketRef)socket; -- (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4; -- (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6; -- (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4; -- (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6; - -// Reading -- (void)doBytesAvailable; -- (void)completeCurrentRead; -- (void)endCurrentRead; -- (void)scheduleDequeueRead; -- (void)maybeDequeueRead; -- (void)doReadTimeout:(NSTimer *)timer; - -// Writing -- (void)doSendBytes; -- (void)completeCurrentWrite; -- (void)endCurrentWrite; -- (void)scheduleDequeueWrite; -- (void)maybeDequeueWrite; -- (void)maybeScheduleDisconnect; -- (void)doWriteTimeout:(NSTimer *)timer; - -// Run Loop -- (void)runLoopAddSource:(CFRunLoopSourceRef)source; -- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; -- (void)runLoopAddTimer:(NSTimer *)timer; -- (void)runLoopRemoveTimer:(NSTimer *)timer; -- (void)runLoopUnscheduleReadStream; -- (void)runLoopUnscheduleWriteStream; - -// Security -- (void)maybeStartTLS; -- (void)onTLSHandshakeSuccessful; - -// Callbacks -- (void)doCFCallback:(CFSocketCallBackType)type - forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; -- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; -- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; - -@end - -static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); -static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo); -static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The AsyncReadPacket encompasses the instructions for any given read. - * The content of a read packet allows the code to determine if we're: - * - reading to a certain length - * - reading to a certain separator - * - or simply reading the first chunk of available data -**/ -@interface AsyncReadPacket : NSObject -{ - @public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - 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; - -- (NSUInteger)readLengthForNonTerm; -- (NSUInteger)readLengthForTerm; -- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr; - -- (NSUInteger)prebufferReadLengthForTerm; -- (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes; -@end - -@implementation AsyncReadPacket - -- (id)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])) - { - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - } - return self; -} - -/** - * For read packets without a set terminator, returns the safe length of data that can be read - * without exceeding the maxLength, or forcing a resize of the buffer if at all possible. -**/ -- (NSUInteger)readLengthForNonTerm -{ - NSAssert(term == nil, @"This method does not apply to term reads"); - - if (readLength > 0) - { - // Read a specific length of data - - return readLength - bytesDone; - - // No need to avoid resizing the buffer. - // It should be resized if the buffer space is less than the requested read length. - } - else - { - // Read all available data - - NSUInteger result = READALL_CHUNKSIZE; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - if (!bufferOwner) - { - // We did NOT create the buffer. - // It is owned by the caller. - // Avoid resizing the buffer if at all possible. - - if ([buffer length] == originalBufferLength) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffSpace = buffSize - startOffset - bytesDone; - - if (buffSpace > 0) - { - result = MIN(result, buffSpace); - } - } - } - - return result; - } -} - -/** - * For read packets with a set terminator, returns the safe length of data that can be read - * without going over a terminator, or the maxLength, or forcing a resize of the buffer if at all possible. - * - * It is assumed the terminator has not already been read. -**/ -- (NSUInteger)readLengthForTerm -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - // What we're going to do is look for a partial sequence of the terminator at the end of the buffer. - // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term, - // and we can only read that amount. - // Otherwise, we're safe to read the entire length of the term. - - NSUInteger termLength = [term length]; - - // Shortcuts - if (bytesDone == 0) return termLength; - if (termLength == 1) return termLength; - - // i = index within buffer at which to check data - // j = length of term to check against - - NSUInteger i, j; - if (bytesDone >= termLength) - { - i = bytesDone - termLength + 1; - j = termLength - 1; - } - else - { - i = 0; - j = bytesDone; - } - - NSUInteger result = termLength; - - void *buf = [buffer mutableBytes]; - const void *termBuf = [term bytes]; - - while (i < bytesDone) - { - void *subbuf = buf + startOffset + i; - - if (memcmp(subbuf, termBuf, j) == 0) - { - result = termLength - j; - break; - } - - i++; - j--; - } - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - if (!bufferOwner) - { - // We did NOT create the buffer. - // It is owned by the caller. - // Avoid resizing the buffer if at all possible. - - if ([buffer length] == originalBufferLength) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffSpace = buffSize - startOffset - bytesDone; - - if (buffSpace > 0) - { - result = MIN(result, buffSpace); - } - } - } - - return result; -} - -/** - * For read packets with a set terminator, - * returns the safe length of data that can be read from the given preBuffer, - * without going over a terminator or the maxLength. - * - * It is assumed the terminator has not already been read. -**/ -- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a portion of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer length]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } - - Byte seq[termLength]; - const void *termBuf = [term bytes]; - - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - void *buf = [buffer mutableBytes] + startOffset + bytesDone - bufLen; - - NSUInteger preLen = termLength - bufLen; - void *pre = (void *)[preBuffer bytes]; - - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - - NSUInteger result = preBufferLength; - - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer - - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); - - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } - - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer - - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer bytes]; // pointer arithmetic - - result = preOffset + termLength; - found = YES; - break; - } - - pre++; - } - } - - // There is no need to avoid resizing the buffer in this particular situation. - - if (foundPtr) *foundPtr = found; - return result; -} - -/** - * Assuming pre-buffering is enabled, returns the amount of data that can be read - * without going over the maxLength. -**/ -- (NSUInteger)prebufferReadLengthForTerm -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - - NSUInteger result = READALL_CHUNKSIZE; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - if (!bufferOwner) - { - // We did NOT create the buffer. - // It is owned by the caller. - // Avoid resizing the buffer if at all possible. - - if ([buffer length] == originalBufferLength) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffSpace = buffSize - startOffset - bytesDone; - - if (buffSpace > 0) - { - result = MIN(result, buffSpace); - } - } - } - - return result; -} - -/** - * For read packets with a set terminator, scans the packet buffer for the term. - * It is assumed the terminator had not been fully read prior to the new bytes. - * - * If the term is found, the number of excess bytes after the term are returned. - * If the term is not found, this method will return -1. - * - * Note: A return value of zero means the term was found at the very end. -**/ -- (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes -{ - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesDone >= numBytes, @"Invoked with invalid numBytes!"); - - // We try to start the search such that the first new byte read matches up with the last byte of the term. - // We continue searching forward after this until the term no longer fits into the buffer. - - NSUInteger termLength = [term length]; - const void *termBuffer = [term bytes]; - - // Remember: This method is called after the bytesDone variable has been updated. - - NSUInteger prevBytesDone = bytesDone - numBytes; - - NSUInteger i; - if (prevBytesDone >= termLength) - i = prevBytesDone - termLength + 1; - else - i = 0; - - while ((i + termLength) <= bytesDone) - { - void *subBuffer = [buffer mutableBytes] + startOffset + i; - - if(memcmp(subBuffer, termBuffer, termLength) == 0) - { - return bytesDone - (i + termLength); - } - - i++; - } - - return -1; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The AsyncWritePacket encompasses the instructions for any given write. -**/ -@interface AsyncWritePacket : NSObject -{ - @public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; -} -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation AsyncWritePacket - -- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; - timeout = t; - tag = i; - bytesDone = 0; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. - * This class my be altered to support more than just TLS in the future. -**/ -@interface AsyncSpecialPacket : NSObject -{ - @public - NSDictionary *tlsSettings; -} -- (id)initWithTLSSettings:(NSDictionary *)settings; -@end - -@implementation AsyncSpecialPacket - -- (id)initWithTLSSettings:(NSDictionary *)settings -{ - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; -} - - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation AsyncSocket - -- (id)init -{ - return [self initWithDelegate:nil userData:0]; -} - -- (id)initWithDelegate:(id)delegate -{ - return [self initWithDelegate:delegate userData:0]; -} - -// Designated initializer. -- (id)initWithDelegate:(id)delegate userData:(long)userData -{ - if((self = [super init])) - { - theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0; - theDelegate = delegate; - theUserData = userData; - - theNativeSocket4 = 0; - theNativeSocket6 = 0; - - theSocket4 = NULL; - theSource4 = NULL; - - theSocket6 = NULL; - theSource6 = NULL; - - theRunLoop = NULL; - theReadStream = NULL; - theWriteStream = NULL; - - theConnectTimer = nil; - - theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; - theCurrentRead = nil; - theReadTimer = nil; - - partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE]; - - theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; - theCurrentWrite = nil; - theWriteTimer = nil; - - // Socket context - NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext"); - theContext.version = 0; - theContext.info = (__bridge void *)(self); - theContext.retain = nil; - theContext.release = nil; - theContext.copyDescription = nil; - - // Default run loop modes - theRunLoopModes = [NSArray arrayWithObject:NSDefaultRunLoopMode]; - } - return self; -} - -// The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. -- (void)dealloc -{ - [self close]; - [NSObject cancelPreviousPerformRequestsWithTarget:self]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Thread-Safety -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)checkForThreadSafety -{ - if (theRunLoop && (theRunLoop != CFRunLoopGetCurrent())) - { - // AsyncSocket is RunLoop based. - // It is designed to be run and accessed from a particular thread/runloop. - // As such, it is faster as it does not have the overhead of locks/synchronization. - // - // However, this places a minimal requirement on the developer to maintain thread-safety. - // If you are seeing errors or crashes in AsyncSocket, - // it is very likely that thread-safety has been broken. - // This method may be enabled via the DEBUG_THREAD_SAFETY macro, - // and will allow you to discover the place in your code where thread-safety is being broken. - // - // Note: - // - // If you find you constantly need to access your socket from various threads, - // you may prefer to use GCDAsyncSocket which is thread-safe. - - [NSException raise:AsyncSocketException - format:@"Attempting to access AsyncSocket instance from incorrect thread."]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accessors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (long)userData -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return theUserData; -} - -- (void)setUserData:(long)userData -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theUserData = userData; -} - -- (id)delegate -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return theDelegate; -} - -- (void)setDelegate:(id)delegate -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theDelegate = delegate; -} - -- (BOOL)canSafelySetDelegate -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); -} - -- (CFSocketRef)getCFSocket -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(theSocket4) - return theSocket4; - else - return theSocket6; -} - -- (CFReadStreamRef)getCFReadStream -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return theReadStream; -} - -- (CFWriteStreamRef)getCFWriteStream -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return theWriteStream; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Progress -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - // Check to make sure we're actually reading something right now, - // and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS). - if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]]) - { - if (tag != NULL) *tag = 0; - if (done != NULL) *done = 0; - if (total != NULL) *total = 0; - - return NAN; - } - - // It's only possible to know the progress of our read if we're reading to a certain length. - // 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 d = theCurrentRead->bytesDone; - NSUInteger t = theCurrentRead->readLength; - - if (tag != NULL) *tag = theCurrentRead->tag; - if (done != NULL) *done = d; - if (total != NULL) *total = t; - - if (t > 0.0) - return (float)d / (float)t; - else - return 1.0F; -} - -- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - // Check to make sure we're actually writing something right now, - // and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS). - if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]]) - { - if (tag != NULL) *tag = 0; - if (done != NULL) *done = 0; - if (total != NULL) *total = 0; - - return NAN; - } - - NSUInteger d = theCurrentWrite->bytesDone; - NSUInteger t = [theCurrentWrite->buffer length]; - - if (tag != NULL) *tag = theCurrentWrite->tag; - if (done != NULL) *done = d; - if (total != NULL) *total = t; - - return (float)d / (float)t; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Run Loop -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)runLoopAddSource:(CFRunLoopSourceRef)source -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopAddSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode -{ - CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); -} - -- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode -{ - CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); -} - -- (void)runLoopAddTimer:(NSTimer *)timer -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopRemoveTimer:(NSTimer *)timer -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopAddTimer:(NSTimer *)timer mode:(NSString *)runLoopMode -{ - CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); -} - -- (void)runLoopRemoveTimer:(NSTimer *)timer mode:(NSString *)runLoopMode -{ - CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); -} - -- (void)runLoopUnscheduleReadStream -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, (__bridge CFStringRef)runLoopMode); - } - CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); -} - -- (void)runLoopUnscheduleWriteStream -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, (__bridge CFStringRef)runLoopMode); - } - CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); -} - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * See the header file for a full explanation of pre-buffering. -**/ -- (void)enablePreBuffering -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theFlags |= kEnablePreBuffering; -} - -/** - * See the header file for a full explanation of this method. -**/ -- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"moveToRunLoop must be called from within the current RunLoop!"); - - if(runLoop == nil) - { - return NO; - } - if(theRunLoop == [runLoop getCFRunLoop]) - { - return YES; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueReadScheduled; - theFlags &= ~kDequeueWriteScheduled; - - if(theReadStream && theWriteStream) - { - [self runLoopUnscheduleReadStream]; - [self runLoopUnscheduleWriteStream]; - } - - if(theSource4) [self runLoopRemoveSource:theSource4]; - if(theSource6) [self runLoopRemoveSource:theSource6]; - - if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; - if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; - - theRunLoop = [runLoop getCFRunLoop]; - - if(theReadTimer) [self runLoopAddTimer:theReadTimer]; - if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; - - if(theSource4) [self runLoopAddSource:theSource4]; - if(theSource6) [self runLoopAddSource:theSource6]; - - if(theReadStream && theWriteStream) - { - if(![self attachStreamsToRunLoop:runLoop error:nil]) - { - return NO; - } - } - - [runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes]; - [runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes]; - [runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes]; - - return YES; -} - -/** - * See the header file for a full explanation of this method. -**/ -- (BOOL)setRunLoopModes:(NSArray *)runLoopModes -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"setRunLoopModes must be called from within the current RunLoop!"); - - if([runLoopModes count] == 0) - { - return NO; - } - if([theRunLoopModes isEqualToArray:runLoopModes]) - { - return YES; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueReadScheduled; - theFlags &= ~kDequeueWriteScheduled; - - if(theReadStream && theWriteStream) - { - [self runLoopUnscheduleReadStream]; - [self runLoopUnscheduleWriteStream]; - } - - if(theSource4) [self runLoopRemoveSource:theSource4]; - if(theSource6) [self runLoopRemoveSource:theSource6]; - - if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; - if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; - - theRunLoopModes = [runLoopModes copy]; - - if(theReadTimer) [self runLoopAddTimer:theReadTimer]; - if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; - - if(theSource4) [self runLoopAddSource:theSource4]; - if(theSource6) [self runLoopAddSource:theSource6]; - - if(theReadStream && theWriteStream) - { - // Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop. - // So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter. - // Instead we pass nil, which will result in the method properly using the current run loop. - - if(![self attachStreamsToRunLoop:nil error:nil]) - { - return NO; - } - } - - [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - - return YES; -} - -- (BOOL)addRunLoopMode:(NSString *)runLoopMode -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"addRunLoopMode must be called from within the current RunLoop!"); - - if(runLoopMode == nil) - { - return NO; - } - if([theRunLoopModes containsObject:runLoopMode]) - { - return YES; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueReadScheduled; - theFlags &= ~kDequeueWriteScheduled; - - NSArray *newRunLoopModes = [theRunLoopModes arrayByAddingObject:runLoopMode]; - theRunLoopModes = newRunLoopModes; - - if(theReadTimer) [self runLoopAddTimer:theReadTimer mode:runLoopMode]; - if(theWriteTimer) [self runLoopAddTimer:theWriteTimer mode:runLoopMode]; - - if(theSource4) [self runLoopAddSource:theSource4 mode:runLoopMode]; - if(theSource6) [self runLoopAddSource:theSource6 mode:runLoopMode]; - - if(theReadStream && theWriteStream) - { - CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); - CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); - } - - [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - - return YES; -} - -- (BOOL)removeRunLoopMode:(NSString *)runLoopMode -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"addRunLoopMode must be called from within the current RunLoop!"); - - if(runLoopMode == nil) - { - return NO; - } - if(![theRunLoopModes containsObject:runLoopMode]) - { - return YES; - } - - NSMutableArray *newRunLoopModes = [theRunLoopModes mutableCopy]; - [newRunLoopModes removeObject:runLoopMode]; - - if([newRunLoopModes count] == 0) - { - return NO; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueReadScheduled; - theFlags &= ~kDequeueWriteScheduled; - - theRunLoopModes = [newRunLoopModes copy]; - - if(theReadTimer) [self runLoopRemoveTimer:theReadTimer mode:runLoopMode]; - if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer mode:runLoopMode]; - - if(theSource4) [self runLoopRemoveSource:theSource4 mode:runLoopMode]; - if(theSource6) [self runLoopRemoveSource:theSource6 mode:runLoopMode]; - - if(theReadStream && theWriteStream) - { - CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); - CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); - } - - [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - - return YES; -} - -- (NSArray *)runLoopModes -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return theRunLoopModes; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accepting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr -{ - return [self acceptOnInterface:nil port:port error:errPtr]; -} - -/** - * To accept on a certain interface, pass the address to accept on. - * To accept on any interface, pass nil or an empty string. - * To accept only connections from localhost pass "localhost" or "loopback". -**/ -- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr -{ - if (theDelegate == NULL) - { - [NSException raise:AsyncSocketException - format:@"Attempting to accept without a delegate. Set a delegate first."]; - } - - if (![self isDisconnected]) - { - [NSException raise:AsyncSocketException - format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; - } - - // Clear queues (spurious read/write requests post disconnect) - [self emptyQueues]; - - // Set up the listen sockaddr structs if needed. - - NSData *address4 = nil, *address6 = nil; - if(interface == nil || ([interface length] == 0)) - { - // Accept on ANY 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_ANY); - 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_any; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures for CFSocketSetAddress. - address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // Accept only on 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 for CFSocketSetAddress. - address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - 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; - hints.ai_flags = AI_PASSIVE; - - int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0); - - if (error) - { - if (errPtr) - { - NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; - } - } - else - { - for (res = res0; res; res = res->ai_next) - { - if (!address4 && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structures for CFSocketSetAddress. - address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if (!address6 && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structures for CFSocketSetAddress. - address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - - if(!address4 && !address6) return NO; - } - - // Create the sockets. - - if (address4) - { - theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr]; - if (theSocket4 == NULL) goto Failed; - } - - if (address6) - { - theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr]; - - // Note: The iPhone doesn't currently support IPv6 - -#if !TARGET_OS_IPHONE - if (theSocket6 == NULL) goto Failed; -#endif - } - - // Attach the sockets to the run loop so that callback methods work - - [self attachSocketsToRunLoop:nil error:nil]; - - // Set the SO_REUSEADDR flags. - - int reuseOn = 1; - if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - - // Set the local bindings which causes the sockets to start listening. - - CFSocketError err; - if (theSocket4) - { - err = CFSocketSetAddress(theSocket4, (__bridge CFDataRef)address4); - if (err != kCFSocketSuccess) goto Failed; - - //NSLog(@"theSocket4: %hu", [self localPortFromCFSocket4:theSocket4]); - } - - if(port == 0 && theSocket4 && theSocket6) - { - // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them - // However, the kernel will choose a different port for both theSocket4 and theSocket6 - // So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6 - UInt16 chosenPort = [self localPortFromCFSocket4:theSocket4]; - - struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; - if (pSockAddr6) // If statement to quiet the static analyzer - { - pSockAddr6->sin6_port = htons(chosenPort); - } - } - - if (theSocket6) - { - err = CFSocketSetAddress(theSocket6, (__bridge CFDataRef)address6); - if (err != kCFSocketSuccess) goto Failed; - - //NSLog(@"theSocket6: %hu", [self localPortFromCFSocket6:theSocket6]); - } - - theFlags |= kDidStartDelegate; - return YES; - -Failed: - if(errPtr) *errPtr = [self getSocketError]; - if(theSocket4 != NULL) - { - CFSocketInvalidate(theSocket4); - CFRelease(theSocket4); - theSocket4 = NULL; - } - if(theSocket6 != NULL) - { - CFSocketInvalidate(theSocket6); - CFRelease(theSocket6); - theSocket6 = NULL; - } - return NO; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Connecting -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr -{ - return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr]; -} - -/** - * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. - * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. - * - * Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection, - * specifically in the onSocketWillConnect: method. -**/ -- (BOOL)connectToHost:(NSString *)hostname - onPort:(UInt16)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - if (theDelegate == NULL) - { - [NSException raise:AsyncSocketException - format:@"Attempting to connect without a delegate. Set a delegate first."]; - } - - if (![self isDisconnected]) - { - [NSException raise:AsyncSocketException - format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; - } - - // Clear queues (spurious read/write requests post disconnect) - [self emptyQueues]; - - if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed; - if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed; - if(![self configureStreamsAndReturnError:errPtr]) goto Failed; - if(![self openStreamsAndReturnError:errPtr]) goto Failed; - - [self startConnectTimeout:timeout]; - theFlags |= kDidStartDelegate; - - return YES; - -Failed: - [self close]; - return NO; -} - -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:-1 error:errPtr]; -} - -/** - * This method creates an initial CFSocket to the given address. - * The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be - * created from the low-level sockets after the connection succeeds. - * - * Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection, - * specifically in the onSocketWillConnect: method. - * - * Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from - * NSNetService addresses method. - * If you have an existing struct sockaddr you can convert it to an NSData object like so: - * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; - * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -{ - return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:timeout error:errPtr]; -} - -/** - * This method is similar to the one above, but allows you to specify which socket interface - * the connection should run over. E.g. ethernet, wifi, bluetooth, etc. -**/ -- (BOOL)connectToAddress:(NSData *)remoteAddr - viaInterfaceAddress:(NSData *)interfaceAddr - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - if (theDelegate == NULL) - { - [NSException raise:AsyncSocketException - format:@"Attempting to connect without a delegate. Set a delegate first."]; - } - - if (![self isDisconnected]) - { - [NSException raise:AsyncSocketException - format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; - } - - // Clear queues (spurious read/write requests post disconnect) - [self emptyQueues]; - - if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed; - if(![self bindSocketToAddress:interfaceAddr error:errPtr]) goto Failed; - if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed; - if(![self configureSocketAndReturnError:errPtr]) goto Failed; - if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed; - - [self startConnectTimeout:timeout]; - theFlags |= kDidStartDelegate; - - return YES; - -Failed: - [self close]; - return NO; -} - -- (void)startConnectTimeout:(NSTimeInterval)timeout -{ - if(timeout >= 0.0) - { - theConnectTimer = [NSTimer timerWithTimeInterval:timeout - target:self - selector:@selector(doConnectTimeout:) - userInfo:nil - repeats:NO]; - [self runLoopAddTimer:theConnectTimer]; - } -} - -- (void)endConnectTimeout -{ - [theConnectTimer invalidate]; - theConnectTimer = nil; -} - -- (void)doConnectTimeout:(NSTimer *)timer -{ - #pragma unused(timer) - - [self endConnectTimeout]; - [self closeWithError:[self getConnectTimeoutError]]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Socket Implementation -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Creates the accept sockets. - * Returns true if either IPv4 or IPv6 is created. - * If either is missing, an error is returned (even though the method may return true). -**/ -- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr -{ - struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; - int addressFamily = pSockAddr->sa_family; - - CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault, - addressFamily, - SOCK_STREAM, - 0, - kCFSocketAcceptCallBack, // Callback flags - (CFSocketCallBack)&MyCFSocketCallback, // Callback method - &theContext); - - if(theSocket == NULL) - { - if(errPtr) *errPtr = [self getSocketError]; - } - - return theSocket; -} - -- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes]; - - if(pSockAddr->sa_family == AF_INET) - { - theSocket4 = CFSocketCreate(NULL, // Default allocator - PF_INET, // Protocol Family - SOCK_STREAM, // Socket Type - IPPROTO_TCP, // Protocol - kCFSocketConnectCallBack, // Callback flags - (CFSocketCallBack)&MyCFSocketCallback, // Callback method - &theContext); // Socket Context - - if(theSocket4 == NULL) - { - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - } - else if(pSockAddr->sa_family == AF_INET6) - { - theSocket6 = CFSocketCreate(NULL, // Default allocator - PF_INET6, // Protocol Family - SOCK_STREAM, // Socket Type - IPPROTO_TCP, // Protocol - kCFSocketConnectCallBack, // Callback flags - (CFSocketCallBack)&MyCFSocketCallback, // Callback method - &theContext); // Socket Context - - if(theSocket6 == NULL) - { - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - } - else - { - if (errPtr) - { - NSString *errMsg = @"Remote address is not IPv4 or IPv6"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; - } - return NO; - } - - return YES; -} - -- (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr -{ - if (interfaceAddr == nil) return YES; - - struct sockaddr *pSockAddr = (struct sockaddr *)[interfaceAddr bytes]; - - CFSocketRef theSocket = (theSocket4 != NULL) ? theSocket4 : theSocket6; - NSAssert((theSocket != NULL), @"bindSocketToAddress called without valid socket"); - - CFSocketNativeHandle nativeSocket = CFSocketGetNative(theSocket); - - if (pSockAddr->sa_family == AF_INET || pSockAddr->sa_family == AF_INET6) - { - int result = bind(nativeSocket, pSockAddr, (socklen_t)[interfaceAddr length]); - if (result != 0) - { - if (errPtr) *errPtr = [self getErrnoError]; - return NO; - } - } - else - { - if (errPtr) - { - NSString *errMsg = @"Interface address is not IPv4 or IPv6"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; - } - return NO; - } - - return YES; -} - -/** - * Adds the CFSocket's to the run-loop so that callbacks will work properly. -**/ -- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr -{ - #pragma unused(errPtr) - - // Get the CFRunLoop to which the socket should be attached. - theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; - - if(theSocket4) - { - theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0); - [self runLoopAddSource:theSource4]; - } - - if(theSocket6) - { - theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); - [self runLoopAddSource:theSource6]; - } - - return YES; -} - -/** - * Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect. - * Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened. -**/ -- (BOOL)configureSocketAndReturnError:(NSError **)errPtr -{ - // Call the delegate method for further configuration. - if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) - { - if([theDelegate onSocketWillConnect:self] == NO) - { - if (errPtr) *errPtr = [self getAbortError]; - return NO; - } - } - return YES; -} - -- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - // Start connecting to the given address in the background - // The MyCFSocketCallback method will be called when the connection succeeds or fails - if(theSocket4) - { - CFSocketError err = CFSocketConnectToAddress(theSocket4, (__bridge CFDataRef)remoteAddr, -1); - if(err != kCFSocketSuccess) - { - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - } - else if(theSocket6) - { - CFSocketError err = CFSocketConnectToAddress(theSocket6, (__bridge CFDataRef)remoteAddr, -1); - if(err != kCFSocketSuccess) - { - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - } - - return YES; -} - -/** - * Attempt to make the new socket. - * If an error occurs, ignore this event. -**/ -- (void)doAcceptFromSocket:(CFSocketRef)parentSocket withNewNativeSocket:(CFSocketNativeHandle)newNativeSocket -{ - if(newNativeSocket) - { - // New socket inherits same delegate and run loop modes. - // Note: We use [self class] to support subclassing AsyncSocket. - AsyncSocket *newSocket = [[[self class] alloc] initWithDelegate:theDelegate]; - [newSocket setRunLoopModes:theRunLoopModes]; - - if (![newSocket createStreamsFromNative:newNativeSocket error:nil]) - { - [newSocket close]; - return; - } - - if (parentSocket == theSocket4) - newSocket->theNativeSocket4 = newNativeSocket; - else - newSocket->theNativeSocket6 = newNativeSocket; - - if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) - [theDelegate onSocket:self didAcceptNewSocket:newSocket]; - - newSocket->theFlags |= kDidStartDelegate; - - NSRunLoop *runLoop = nil; - if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) - { - runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; - } - - if(![newSocket attachStreamsToRunLoop:runLoop error:nil]) goto Failed; - if(![newSocket configureStreamsAndReturnError:nil]) goto Failed; - if(![newSocket openStreamsAndReturnError:nil]) goto Failed; - - return; - - Failed: - [newSocket close]; - } -} - -/** - * This method is called as a result of connectToAddress:withTimeout:error:. - * At this point we have an open CFSocket from which we need to create our read and write stream. -**/ -- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError -{ - NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); - - if(socketError == kCFSocketTimeout || socketError == kCFSocketError) - { - [self closeWithError:[self getSocketError]]; - return; - } - - // Get the underlying native (BSD) socket - CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock); - - // Store a reference to it - if (sock == theSocket4) - theNativeSocket4 = nativeSocket; - else - theNativeSocket6 = nativeSocket; - - // Setup the CFSocket so that invalidating it will not close the underlying native socket - CFSocketSetSocketFlags(sock, 0); - - // Invalidate and release the CFSocket - All we need from here on out is the nativeSocket. - // Note: If we don't invalidate the CFSocket (leaving the native socket open) - // then theReadStream and theWriteStream won't function properly. - // Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted. - // - // This is likely due to the mixture of the CFSocketCreateWithNative method, - // along with the CFStreamCreatePairWithSocket method. - // The documentation for CFSocketCreateWithNative states: - // - // If a CFSocket object already exists for sock, - // the function returns the pre-existing object instead of creating a new object; - // the context, callout, and callBackTypes parameters are ignored in this case. - // - // So the CFStreamCreateWithNative method invokes the CFSocketCreateWithNative method, - // thinking that is creating a new underlying CFSocket for it's own purposes. - // When it does this, it uses the context/callout/callbackTypes parameters to setup everything appropriately. - // However, if a CFSocket already exists for the native socket, - // then it is returned (as per the documentation), which in turn screws up the CFStreams. - - CFSocketInvalidate(sock); - CFRelease(sock); - theSocket4 = NULL; - theSocket6 = NULL; - - NSError *err; - BOOL pass = YES; - - if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO; - if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO; - if(pass && ![self openStreamsAndReturnError:&err]) pass = NO; - - if(!pass) - { - [self closeWithError:err]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Stream Implementation -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Creates the CFReadStream and CFWriteStream from the given native socket. - * The CFSocket may be extracted from either stream after the streams have been opened. - * - * Note: The given native socket must already be connected! -**/ -- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr -{ - // Create the socket & streams. - CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); - if (theReadStream == NULL || theWriteStream == NULL) - { - NSError *err = [self getStreamError]; - - NSLog(@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err); - - if (errPtr) *errPtr = err; - return NO; - } - - // Ensure the CF & BSD socket is closed when the streams are closed. - CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - - return YES; -} - -/** - * Creates the CFReadStream and CFWriteStream from the given hostname and port number. - * The CFSocket may be extracted from either stream after the streams have been opened. -**/ -- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr -{ - // Create the socket & streams. - CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostname, port, &theReadStream, &theWriteStream); - if (theReadStream == NULL || theWriteStream == NULL) - { - if (errPtr) *errPtr = [self getStreamError]; - return NO; - } - - // Ensure the CF & BSD socket is closed when the streams are closed. - CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); - - return YES; -} - -- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr -{ - // Get the CFRunLoop to which the socket should be attached. - theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; - - // Setup read stream callbacks - - CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | - kCFStreamEventErrorOccurred | - kCFStreamEventEndEncountered | - kCFStreamEventOpenCompleted; - - if (!CFReadStreamSetClient(theReadStream, - readStreamEvents, - (CFReadStreamClientCallBack)&MyCFReadStreamCallback, - (CFStreamClientContext *)(&theContext))) - { - NSError *err = [self getStreamError]; - - NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); - NSLog (@"Error: %@", err); - - if (errPtr) *errPtr = err; - return NO; - } - - // Setup write stream callbacks - - CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes | - kCFStreamEventErrorOccurred | - kCFStreamEventEndEncountered | - kCFStreamEventOpenCompleted; - - if (!CFWriteStreamSetClient (theWriteStream, - writeStreamEvents, - (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, - (CFStreamClientContext *)(&theContext))) - { - NSError *err = [self getStreamError]; - - NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); - NSLog (@"Error: %@", err); - - if (errPtr) *errPtr = err; - return NO; - } - - // Add read and write streams to run loop - - for (NSString *runLoopMode in theRunLoopModes) - { - CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, (__bridge CFStringRef)runLoopMode); - CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, (__bridge CFStringRef)runLoopMode); - } - - return YES; -} - -/** - * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. - * - * If being called from a connect method, - * the CFSocket and CFNativeSocket will not be available until after the connection is opened. -**/ -- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr -{ - // Call the delegate method for further configuration. - if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) - { - if([theDelegate onSocketWillConnect:self] == NO) - { - if (errPtr) *errPtr = [self getAbortError]; - return NO; - } - } - return YES; -} - -- (BOOL)openStreamsAndReturnError:(NSError **)errPtr -{ - BOOL pass = YES; - - if(pass && !CFReadStreamOpen(theReadStream)) - { - NSLog (@"AsyncSocket %p couldn't open read stream,", self); - pass = NO; - } - - if(pass && !CFWriteStreamOpen(theWriteStream)) - { - NSLog (@"AsyncSocket %p couldn't open write stream,", self); - pass = NO; - } - - if(!pass) - { - if (errPtr) *errPtr = [self getStreamError]; - } - - return pass; -} - -/** - * Called when read or write streams open. - * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. -**/ -- (void)doStreamOpen -{ - if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite)) - { - NSError *err = nil; - - // Get the socket - if (![self setSocketFromStreamsAndReturnError: &err]) - { - NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); - [self closeWithError:err]; - return; - } - - // Stop the connection attempt timeout timer - [self endConnectTimeout]; - - if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) - { - [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; - } - - // Immediately deal with any already-queued requests. - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } -} - -- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr -{ - // Get the CFSocketNativeHandle from theReadStream - CFSocketNativeHandle native; - CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); - if(nativeProp == NULL) - { - if (errPtr) *errPtr = [self getStreamError]; - return NO; - } - - CFIndex nativePropLen = CFDataGetLength(nativeProp); - CFIndex nativeLen = (CFIndex)sizeof(native); - - CFIndex len = MIN(nativePropLen, nativeLen); - - CFDataGetBytes(nativeProp, CFRangeMake(0, len), (UInt8 *)&native); - CFRelease(nativeProp); - - CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); - if(theSocket == NULL) - { - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - - // Determine whether the connection was IPv4 or IPv6. - // We may already know if this was an accepted socket, - // or if the connectToAddress method was used. - // In either of the above two cases, the native socket variable would already be set. - - if (theNativeSocket4 > 0) - { - theSocket4 = theSocket; - return YES; - } - if (theNativeSocket6 > 0) - { - theSocket6 = theSocket; - return YES; - } - - CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); - if(peeraddr == NULL) - { - NSLog(@"AsyncSocket couldn't determine IP version of socket"); - - CFRelease(theSocket); - - if (errPtr) *errPtr = [self getSocketError]; - return NO; - } - struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr); - - if(sa->sa_family == AF_INET) - { - theSocket4 = theSocket; - theNativeSocket4 = native; - } - else - { - theSocket6 = theSocket; - theNativeSocket6 = native; - } - - CFRelease(peeraddr); - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnect Implementation -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Sends error message and disconnects -- (void)closeWithError:(NSError *)err -{ - theFlags |= kClosingWithError; - - if (theFlags & kDidStartDelegate) - { - // Try to salvage what data we can. - [self recoverUnreadData]; - - // Let the delegate know, so it can try to recover if it likes. - if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) - { - [theDelegate onSocket:self willDisconnectWithError:err]; - } - } - [self close]; -} - -// Prepare partially read data for recovery. -- (void)recoverUnreadData -{ - if(theCurrentRead != nil) - { - // We never finished the current read. - // Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet. - - if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0)) - { - // We need to move its data into the front of the partial read buffer. - - void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; - - [partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0) - withBytes:buffer - length:theCurrentRead->bytesDone]; - } - } - - [self emptyQueues]; -} - -- (void)emptyQueues -{ - if (theCurrentRead != nil) [self endCurrentRead]; - if (theCurrentWrite != nil) [self endCurrentWrite]; - - [theReadQueue removeAllObjects]; - [theWriteQueue removeAllObjects]; - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; - - theFlags &= ~kDequeueReadScheduled; - theFlags &= ~kDequeueWriteScheduled; -} - -/** - * Disconnects. This is called for both error and clean disconnections. -**/ -- (void)close -{ - // Empty queues - [self emptyQueues]; - - // Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error) - [partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0]; - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; - - // Stop the connection attempt timeout timer - if (theConnectTimer != nil) - { - [self endConnectTimeout]; - } - - // Close streams. - if (theReadStream != NULL) - { - [self runLoopUnscheduleReadStream]; - CFReadStreamClose(theReadStream); - CFRelease(theReadStream); - theReadStream = NULL; - } - if (theWriteStream != NULL) - { - [self runLoopUnscheduleWriteStream]; - CFWriteStreamClose(theWriteStream); - CFRelease(theWriteStream); - theWriteStream = NULL; - } - - // Close sockets. - if (theSocket4 != NULL) - { - CFSocketInvalidate (theSocket4); - CFRelease (theSocket4); - theSocket4 = NULL; - } - if (theSocket6 != NULL) - { - CFSocketInvalidate (theSocket6); - CFRelease (theSocket6); - theSocket6 = NULL; - } - - // Closing the streams or sockets resulted in closing the underlying native socket - theNativeSocket4 = 0; - theNativeSocket6 = 0; - - // Remove run loop sources - if (theSource4 != NULL) - { - [self runLoopRemoveSource:theSource4]; - CFRelease (theSource4); - theSource4 = NULL; - } - if (theSource6 != NULL) - { - [self runLoopRemoveSource:theSource6]; - CFRelease (theSource6); - theSource6 = NULL; - } - theRunLoop = 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 = (theFlags & kDidStartDelegate); - - // Clear all flags (except the pre-buffering flag, which should remain as is) - theFlags &= kEnablePreBuffering; - - if (shouldCallDelegate) - { - if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) - { - [theDelegate onSocketDidDisconnect:self]; - } - } - - // Do not access any instance variables after calling onSocketDidDisconnect. - // This gives the delegate freedom to release us without returning here and crashing. -} - -/** - * Disconnects immediately. Any pending reads or writes are dropped. -**/ -- (void)disconnect -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - [self close]; -} - -/** - * Diconnects after all pending reads have completed. -**/ -- (void)disconnectAfterReading -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theFlags |= (kForbidReadsWrites | kDisconnectAfterReads); - - [self maybeScheduleDisconnect]; -} - -/** - * Disconnects after all pending writes have completed. -**/ -- (void)disconnectAfterWriting -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites); - - [self maybeScheduleDisconnect]; -} - -/** - * Disconnects after all pending reads and writes have completed. -**/ -- (void)disconnectAfterReadingAndWriting -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - - [self maybeScheduleDisconnect]; -} - -/** - * Schedules a call to disconnect if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. -**/ -- (void)maybeScheduleDisconnect -{ - BOOL shouldDisconnect = NO; - - if(theFlags & kDisconnectAfterReads) - { - if(([theReadQueue count] == 0) && (theCurrentRead == nil)) - { - if(theFlags & kDisconnectAfterWrites) - { - if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - } - } - else if(theFlags & kDisconnectAfterWrites) - { - if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) - { - shouldDisconnect = YES; - } - } - - if(shouldDisconnect) - { - [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -/** - * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read - * any data that's left on the socket. -**/ -- (NSData *)unreadData -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - // Ensure this method will only return data in the event of an error - if (!(theFlags & kClosingWithError)) return nil; - - if (theReadStream == NULL) return nil; - - NSUInteger totalBytesRead = [partialReadBuffer length]; - - BOOL error = NO; - while (!error && CFReadStreamHasBytesAvailable(theReadStream)) - { - if (totalBytesRead == [partialReadBuffer length]) - { - [partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE]; - } - - // Number of bytes to read is space left in packet buffer. - NSUInteger bytesToRead = [partialReadBuffer length] - totalBytesRead; - - // Read data into packet buffer - UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead ); - - CFIndex result = CFReadStreamRead(theReadStream, packetbuf, bytesToRead); - - // Check results - if (result < 0) - { - error = YES; - } - else - { - CFIndex bytesRead = result; - - totalBytesRead += bytesRead; - } - } - - [partialReadBuffer setLength:totalBytesRead]; - - return partialReadBuffer; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns a standard error object for the current errno value. - * Errno is used for low-level BSD socket errors. -**/ -- (NSError *)getErrnoError -{ - NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -/** - * Returns a standard error message for a CFSocket error. - * Unfortunately, CFSocket offers no feedback on its errors. -**/ -- (NSError *)getSocketError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", - @"AsyncSocket", [NSBundle mainBundle], - @"General CFSocket error", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; -} - -- (NSError *)getStreamError -{ - CFStreamError err; - if (theReadStream != NULL) - { - err = CFReadStreamGetError (theReadStream); - if (err.error != 0) return [self errorFromCFStreamError: err]; - } - - if (theWriteStream != NULL) - { - err = CFWriteStreamGetError (theWriteStream); - if (err.error != 0) return [self errorFromCFStreamError: err]; - } - - return nil; -} - -/** - * Returns a standard AsyncSocket abort error. -**/ -- (NSError *)getAbortError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", - @"AsyncSocket", [NSBundle mainBundle], - @"Connection canceled", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket connect timeout error. -**/ -- (NSError *)getConnectTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError", - @"AsyncSocket", [NSBundle mainBundle], - @"Attempt to connect to host timed out", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket maxed out error. -**/ -- (NSError *)getReadMaxedOutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError", - @"AsyncSocket", [NSBundle mainBundle], - @"Read operation reached set maximum length", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket read timeout error. -**/ -- (NSError *)getReadTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", - @"AsyncSocket", [NSBundle mainBundle], - @"Read operation timed out", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; -} - -/** - * Returns a standard AsyncSocket write timeout error. -**/ -- (NSError *)getWriteTimeoutError -{ - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", - @"AsyncSocket", [NSBundle mainBundle], - @"Write operation timed out", nil); - - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; -} - -- (NSError *)errorFromCFStreamError:(CFStreamError)err -{ - if (err.domain == 0 && err.error == 0) return nil; - - // Can't use switch; these constants aren't int literals. - NSString *domain = @"CFStreamError (unlisted domain)"; - NSString *message = nil; - - if(err.domain == kCFStreamErrorDomainPOSIX) { - domain = NSPOSIXErrorDomain; - } - else if(err.domain == kCFStreamErrorDomainMacOSStatus) { - domain = NSOSStatusErrorDomain; - } - else if(err.domain == kCFStreamErrorDomainMach) { - domain = NSMachErrorDomain; - } - else if(err.domain == kCFStreamErrorDomainNetDB) - { - domain = @"kCFStreamErrorDomainNetDB"; - message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; - } - else if(err.domain == kCFStreamErrorDomainNetServices) { - domain = @"kCFStreamErrorDomainNetServices"; - } - else if(err.domain == kCFStreamErrorDomainSOCKS) { - domain = @"kCFStreamErrorDomainSOCKS"; - } - else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { - domain = @"kCFStreamErrorDomainSystemConfiguration"; - } - else if(err.domain == kCFStreamErrorDomainSSL) { - domain = @"kCFStreamErrorDomainSSL"; - } - - NSDictionary *info = nil; - if(message != nil) - { - info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; - } - return [NSError errorWithDomain:domain code:err.error userInfo:info]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)isDisconnected -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if (theNativeSocket4 > 0) return NO; - if (theNativeSocket6 > 0) return NO; - - if (theSocket4) return NO; - if (theSocket6) return NO; - - if (theReadStream) return NO; - if (theWriteStream) return NO; - - return YES; -} - -- (BOOL)isConnected -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return [self areStreamsConnected]; -} - -- (NSString *)connectedHost -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(theSocket4) - return [self connectedHostFromCFSocket4:theSocket4]; - if(theSocket6) - return [self connectedHostFromCFSocket6:theSocket6]; - - if(theNativeSocket4 > 0) - return [self connectedHostFromNativeSocket4:theNativeSocket4]; - if(theNativeSocket6 > 0) - return [self connectedHostFromNativeSocket6:theNativeSocket6]; - - return nil; -} - -- (UInt16)connectedPort -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(theSocket4) - return [self connectedPortFromCFSocket4:theSocket4]; - if(theSocket6) - return [self connectedPortFromCFSocket6:theSocket6]; - - if(theNativeSocket4 > 0) - return [self connectedPortFromNativeSocket4:theNativeSocket4]; - if(theNativeSocket6 > 0) - return [self connectedPortFromNativeSocket6:theNativeSocket6]; - - return 0; -} - -- (NSString *)localHost -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(theSocket4) - return [self localHostFromCFSocket4:theSocket4]; - if(theSocket6) - return [self localHostFromCFSocket6:theSocket6]; - - if(theNativeSocket4 > 0) - return [self localHostFromNativeSocket4:theNativeSocket4]; - if(theNativeSocket6 > 0) - return [self localHostFromNativeSocket6:theNativeSocket6]; - - return nil; -} - -- (UInt16)localPort -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(theSocket4) - return [self localPortFromCFSocket4:theSocket4]; - if(theSocket6) - return [self localPortFromCFSocket6:theSocket6]; - - if(theNativeSocket4 > 0) - return [self localPortFromNativeSocket4:theNativeSocket4]; - if(theNativeSocket6 > 0) - return [self localPortFromNativeSocket6:theNativeSocket6]; - - return 0; -} - -- (NSString *)connectedHost4 -{ - if(theSocket4) - return [self connectedHostFromCFSocket4:theSocket4]; - if(theNativeSocket4 > 0) - return [self connectedHostFromNativeSocket4:theNativeSocket4]; - - return nil; -} - -- (NSString *)connectedHost6 -{ - if(theSocket6) - return [self connectedHostFromCFSocket6:theSocket6]; - if(theNativeSocket6 > 0) - return [self connectedHostFromNativeSocket6:theNativeSocket6]; - - return nil; -} - -- (UInt16)connectedPort4 -{ - if(theSocket4) - return [self connectedPortFromCFSocket4:theSocket4]; - if(theNativeSocket4 > 0) - return [self connectedPortFromNativeSocket4:theNativeSocket4]; - - return 0; -} - -- (UInt16)connectedPort6 -{ - if(theSocket6) - return [self connectedPortFromCFSocket6:theSocket6]; - if(theNativeSocket6 > 0) - return [self connectedPortFromNativeSocket6:theNativeSocket6]; - - return 0; -} - -- (NSString *)localHost4 -{ - if(theSocket4) - return [self localHostFromCFSocket4:theSocket4]; - if(theNativeSocket4 > 0) - return [self localHostFromNativeSocket4:theNativeSocket4]; - - return nil; -} - -- (NSString *)localHost6 -{ - if(theSocket6) - return [self localHostFromCFSocket6:theSocket6]; - if(theNativeSocket6 > 0) - return [self localHostFromNativeSocket6:theNativeSocket6]; - - return nil; -} - -- (UInt16)localPort4 -{ - if(theSocket4) - return [self localPortFromCFSocket4:theSocket4]; - if(theNativeSocket4 > 0) - return [self localPortFromNativeSocket4:theNativeSocket4]; - - return 0; -} - -- (UInt16)localPort6 -{ - if(theSocket6) - return [self localPortFromCFSocket6:theSocket6]; - if(theNativeSocket6 > 0) - return [self localPortFromNativeSocket6:theNativeSocket6]; - - return 0; -} - -- (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [self hostFromAddress4:&sockaddr4]; -} - -- (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [self hostFromAddress6:&sockaddr6]; -} - -- (NSString *)connectedHostFromCFSocket4:(CFSocketRef)theSocket -{ - CFDataRef peeraddr; - NSString *peerstr = nil; - - if((peeraddr = CFSocketCopyPeerAddress(theSocket))) - { - struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); - - peerstr = [self hostFromAddress4:pSockAddr]; - CFRelease (peeraddr); - } - - return peerstr; -} - -- (NSString *)connectedHostFromCFSocket6:(CFSocketRef)theSocket -{ - CFDataRef peeraddr; - NSString *peerstr = nil; - - if((peeraddr = CFSocketCopyPeerAddress(theSocket))) - { - struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); - - peerstr = [self hostFromAddress6:pSockAddr]; - CFRelease (peeraddr); - } - - return peerstr; -} - -- (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [self portFromAddress4:&sockaddr4]; -} - -- (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [self portFromAddress6:&sockaddr6]; -} - -- (UInt16)connectedPortFromCFSocket4:(CFSocketRef)theSocket -{ - CFDataRef peeraddr; - UInt16 peerport = 0; - - if((peeraddr = CFSocketCopyPeerAddress(theSocket))) - { - struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); - - peerport = [self portFromAddress4:pSockAddr]; - CFRelease (peeraddr); - } - - return peerport; -} - -- (UInt16)connectedPortFromCFSocket6:(CFSocketRef)theSocket -{ - CFDataRef peeraddr; - UInt16 peerport = 0; - - if((peeraddr = CFSocketCopyPeerAddress(theSocket))) - { - struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); - - peerport = [self portFromAddress6:pSockAddr]; - CFRelease (peeraddr); - } - - return peerport; -} - -- (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [self hostFromAddress4:&sockaddr4]; -} - -- (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [self hostFromAddress6:&sockaddr6]; -} - -- (NSString *)localHostFromCFSocket4:(CFSocketRef)theSocket -{ - CFDataRef selfaddr; - NSString *selfstr = nil; - - if((selfaddr = CFSocketCopyAddress(theSocket))) - { - struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); - - selfstr = [self hostFromAddress4:pSockAddr]; - CFRelease (selfaddr); - } - - return selfstr; -} - -- (NSString *)localHostFromCFSocket6:(CFSocketRef)theSocket -{ - CFDataRef selfaddr; - NSString *selfstr = nil; - - if((selfaddr = CFSocketCopyAddress(theSocket))) - { - struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); - - selfstr = [self hostFromAddress6:pSockAddr]; - CFRelease (selfaddr); - } - - return selfstr; -} - -- (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [self portFromAddress4:&sockaddr4]; -} - -- (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket -{ - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [self portFromAddress6:&sockaddr6]; -} - -- (UInt16)localPortFromCFSocket4:(CFSocketRef)theSocket -{ - CFDataRef selfaddr; - UInt16 selfport = 0; - - if ((selfaddr = CFSocketCopyAddress(theSocket))) - { - struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); - - selfport = [self portFromAddress4:pSockAddr]; - CFRelease (selfaddr); - } - - return selfport; -} - -- (UInt16)localPortFromCFSocket6:(CFSocketRef)theSocket -{ - CFDataRef selfaddr; - UInt16 selfport = 0; - - if ((selfaddr = CFSocketCopyAddress(theSocket))) - { - struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); - - selfport = [self portFromAddress6:pSockAddr]; - CFRelease (selfaddr); - } - - return selfport; -} - -- (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv4 address to string."]; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -- (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv6 address to string."]; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -- (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4 -{ - return ntohs(pSockaddr4->sin_port); -} - -- (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6 -{ - return ntohs(pSockaddr6->sin6_port); -} - -- (NSData *)connectedAddress -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - // Extract address from CFSocket - - CFSocketRef theSocket; - - if (theSocket4) - theSocket = theSocket4; - else - theSocket = theSocket6; - - if (theSocket) - { - CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); - - if (peeraddr == NULL) return nil; - - NSData *result = (__bridge_transfer NSData *)peeraddr; - return result; - } - - // Extract address from CFSocketNativeHandle - - socklen_t sockaddrlen; - CFSocketNativeHandle theNativeSocket = 0; - - if (theNativeSocket4 > 0) - { - theNativeSocket = theNativeSocket4; - sockaddrlen = sizeof(struct sockaddr_in); - } - else - { - theNativeSocket = theNativeSocket6; - sockaddrlen = sizeof(struct sockaddr_in6); - } - - NSData *result = nil; - void *sockaddr = malloc(sockaddrlen); - - if(getpeername(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) - { - result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; - } - else - { - free(sockaddr); - } - - return result; -} - -- (NSData *)localAddress -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - // Extract address from CFSocket - - CFSocketRef theSocket; - - if (theSocket4) - theSocket = theSocket4; - else - theSocket = theSocket6; - - if (theSocket) - { - CFDataRef selfaddr = CFSocketCopyAddress(theSocket); - - if (selfaddr == NULL) return nil; - - NSData *result = (__bridge_transfer NSData *)selfaddr; - return result; - } - - // Extract address from CFSocketNativeHandle - - socklen_t sockaddrlen; - CFSocketNativeHandle theNativeSocket = 0; - - if (theNativeSocket4 > 0) - { - theNativeSocket = theNativeSocket4; - sockaddrlen = sizeof(struct sockaddr_in); - } - else - { - theNativeSocket = theNativeSocket6; - sockaddrlen = sizeof(struct sockaddr_in6); - } - - NSData *result = nil; - void *sockaddr = malloc(sockaddrlen); - - if(getsockname(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) - { - result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; - } - else - { - free(sockaddr); - } - - return result; -} - -- (BOOL)isIPv4 -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return (theNativeSocket4 > 0 || theSocket4 != NULL); -} - -- (BOOL)isIPv6 -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - return (theNativeSocket6 > 0 || theSocket6 != NULL); -} - -- (BOOL)areStreamsConnected -{ - CFStreamStatus s; - - if (theReadStream != NULL) - { - s = CFReadStreamGetStatus(theReadStream); - if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) - return NO; - } - else return NO; - - if (theWriteStream != NULL) - { - s = CFWriteStreamGetStatus(theWriteStream); - if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) - return NO; - } - else return NO; - - return YES; -} - -- (NSString *)description -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"}; - CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0; - CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0; - - NSString *peerstr, *selfstr; - - BOOL is4 = [self isIPv4]; - BOOL is6 = [self isIPv6]; - - if (is4 || is6) - { - if (is4 && is6) - { - peerstr = [NSString stringWithFormat: @"%@/%@ %u", - [self connectedHost4], - [self connectedHost6], - [self connectedPort]]; - } - else if (is4) - { - peerstr = [NSString stringWithFormat: @"%@ %u", - [self connectedHost4], - [self connectedPort4]]; - } - else - { - peerstr = [NSString stringWithFormat: @"%@ %u", - [self connectedHost6], - [self connectedPort6]]; - } - } - else peerstr = @"nowhere"; - - if (is4 || is6) - { - if (is4 && is6) - { - selfstr = [NSString stringWithFormat: @"%@/%@ %u", - [self localHost4], - [self localHost6], - [self localPort]]; - } - else if (is4) - { - selfstr = [NSString stringWithFormat: @"%@ %u", - [self localHost4], - [self localPort4]]; - } - else - { - selfstr = [NSString stringWithFormat: @"%@ %u", - [self localHost6], - [self localPort6]]; - } - } - else selfstr = @"nowhere"; - - NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150]; - - [ms appendString:[NSString stringWithFormat:@"readLength > 0) - percentDone = (float)theCurrentRead->bytesDone / (float)theCurrentRead->readLength * 100.0F; - else - percentDone = 100.0F; - - [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", - (unsigned int)[theCurrentRead->buffer length], - theCurrentRead->bytesDone ? percentDone : 0]]; - } - - if (theCurrentWrite == nil || [theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) - [ms appendString: @"no current write, "]; - else - { - int percentDone = (float)theCurrentWrite->bytesDone / (float)[theCurrentWrite->buffer length] * 100.0F; - - [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", - (unsigned int)[theCurrentWrite->buffer length], - theCurrentWrite->bytesDone ? percentDone : 0]]; - } - - [ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]]; - [ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]]; - - if(theFlags & kDisconnectAfterReads) - { - if(theFlags & kDisconnectAfterWrites) - [ms appendString: @", will disconnect after reads & writes"]; - else - [ms appendString: @", will disconnect after reads"]; - } - else if(theFlags & kDisconnectAfterWrites) - { - [ms appendString: @", will disconnect after writes"]; - } - - if (![self isConnected]) [ms appendString: @", not connected"]; - - [ms appendString:@">"]; - - return ms; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reading -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataWithTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if (offset > [buffer length]) return; - if (theFlags & kForbidReadsWrites) return; - - AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; - [theReadQueue addObject:packet]; - [self scheduleDequeueRead]; - -} - -- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; -} - -- (void)readDataToLength:(NSUInteger)length - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if (length == 0) return; - if (offset > [buffer length]) return; - if (theFlags & kForbidReadsWrites) return; - - AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; - [theReadQueue addObject:packet]; - [self scheduleDequeueRead]; - -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; -} - -- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag -{ - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; -} - -- (void)readDataToData:(NSData *)data - withTimeout:(NSTimeInterval)timeout - buffer:(NSMutableData *)buffer - bufferOffset:(NSUInteger)offset - maxLength:(NSUInteger)length - tag:(long)tag -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if (data == nil || [data length] == 0) return; - if (offset > [buffer length]) return; - if (length > 0 && length < [data length]) return; - if (theFlags & kForbidReadsWrites) return; - - AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - [theReadQueue addObject:packet]; - [self scheduleDequeueRead]; - -} - -/** - * Puts a maybeDequeueRead on the run loop. - * An assumption here is that selectors will be performed consecutively within their priority. -**/ -- (void)scheduleDequeueRead -{ - if((theFlags & kDequeueReadScheduled) == 0) - { - theFlags |= kDequeueReadScheduled; - [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -/** - * This method starts a new read, if needed. - * It is called when a user requests a read, - * or when a stream opens that may have requested reads sitting in the queue, etc. -**/ -- (void)maybeDequeueRead -{ - // Unset the flag indicating a call to this method is scheduled - theFlags &= ~kDequeueReadScheduled; - - // If we're not currently processing a read AND we have an available read stream - if((theCurrentRead == nil) && (theReadStream != NULL)) - { - if([theReadQueue count] > 0) - { - // Dequeue the next object in the write queue - theCurrentRead = [theReadQueue objectAtIndex:0]; - [theReadQueue removeObjectAtIndex:0]; - - if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]]) - { - // Attempt to start TLS - theFlags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - // Start time-out timer - if(theCurrentRead->timeout >= 0.0) - { - theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout - target:self - selector:@selector(doReadTimeout:) - userInfo:nil - repeats:NO]; - [self runLoopAddTimer:theReadTimer]; - } - - // Immediately read, if possible - [self doBytesAvailable]; - } - } - else if(theFlags & kDisconnectAfterReads) - { - if(theFlags & kDisconnectAfterWrites) - { - if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) - { - [self disconnect]; - } - } - else - { - [self disconnect]; - } - } - } -} - -/** - * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable(). - * This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag. -**/ -- (BOOL)hasBytesAvailable -{ - if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0)) - { - return YES; - } - else - { - return CFReadStreamHasBytesAvailable(theReadStream); - } -} - -/** - * Call this method in doBytesAvailable instead of CFReadStreamRead(). - * This method support pre-buffering properly. -**/ -- (CFIndex)readIntoBuffer:(void *)buffer maxLength:(NSUInteger)length -{ - if([partialReadBuffer length] > 0) - { - // Determine the maximum amount of data to read - NSUInteger bytesToRead = MIN(length, [partialReadBuffer length]); - - // Copy the bytes from the partial read buffer - memcpy(buffer, [partialReadBuffer bytes], (size_t)bytesToRead); - - // Remove the copied bytes from the partial read buffer - [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0]; - - return (CFIndex)bytesToRead; - } - else - { - // Unset the "has-bytes-available" flag - theFlags &= ~kSocketHasBytesAvailable; - - return CFReadStreamRead(theReadStream, (UInt8 *)buffer, length); - } -} - -/** - * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. -**/ -- (void)doBytesAvailable -{ - // If data is available on the stream, but there is no read request, then we don't need to process the data yet. - // Also, if there is a read request but no read stream setup, we can't process any data yet. - if((theCurrentRead == nil) || (theReadStream == NULL)) - { - return; - } - - // Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet) - - NSUInteger totalBytesRead = 0; - - BOOL done = NO; - BOOL socketError = NO; - BOOL maxoutError = NO; - - while(!done && !socketError && !maxoutError && [self hasBytesAvailable]) - { - BOOL didPreBuffer = NO; - BOOL didReadFromPreBuffer = NO; - - // 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. - - NSUInteger bytesToRead; - - if (theCurrentRead->term != nil) - { - // Read type #3 - read up to a terminator - // - // If pre-buffering is enabled we'll read a chunk and search for the terminator. - // If the terminator is found, overflow data will be placed in the partialReadBuffer for the next read. - // - // If pre-buffering is disabled we'll be forced to read only a few bytes. - // Just enough to ensure we don't go past our term or over our max limit. - // - // If we already have data pre-buffered, we can read directly from it. - - if ([partialReadBuffer length] > 0) - { - didReadFromPreBuffer = YES; - bytesToRead = [theCurrentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done]; - } - else - { - if (theFlags & kEnablePreBuffering) - { - didPreBuffer = YES; - bytesToRead = [theCurrentRead prebufferReadLengthForTerm]; - } - else - { - bytesToRead = [theCurrentRead readLengthForTerm]; - } - } - } - else - { - // Read type #1 or #2 - - bytesToRead = [theCurrentRead readLengthForNonTerm]; - } - - // Make sure we have enough room in the buffer for our read - - NSUInteger buffSize = [theCurrentRead->buffer length]; - NSUInteger buffSpace = buffSize - theCurrentRead->startOffset - theCurrentRead->bytesDone; - - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; - - [theCurrentRead->buffer increaseLengthBy:buffInc]; - } - - // Read data into packet buffer - - void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; - void *subBuffer = buffer + theCurrentRead->bytesDone; - - CFIndex result = [self readIntoBuffer:subBuffer maxLength:bytesToRead]; - - // Check results - if (result < 0) - { - socketError = YES; - } - else - { - CFIndex bytesRead = result; - - // Update total amount read for the current read - theCurrentRead->bytesDone += bytesRead; - - // Update total amount read in this method invocation - totalBytesRead += bytesRead; - - - // Is packet done? - if (theCurrentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (theCurrentRead->bytesDone == theCurrentRead->readLength); - } - else if (theCurrentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (didPreBuffer) - { - // Search for the terminating sequence within the big chunk we just read. - - NSInteger overflow = [theCurrentRead searchForTermAfterPreBuffering:result]; - - if (overflow > 0) - { - // Copy excess data into partialReadBuffer - void *overflowBuffer = buffer + theCurrentRead->bytesDone - overflow; - - [partialReadBuffer appendBytes:overflowBuffer length:overflow]; - - // Update the bytesDone variable. - theCurrentRead->bytesDone -= overflow; - - // Note: The completeCurrentRead method will trim the buffer for us. - } - - done = (overflow >= 0); - } - else if (didReadFromPreBuffer) - { - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - } - else - { - // Search for the terminating sequence at the end of the buffer - - NSUInteger termlen = [theCurrentRead->term length]; - - if(theCurrentRead->bytesDone >= termlen) - { - void *bufferEnd = buffer + (theCurrentRead->bytesDone - termlen); - - const void *seq = [theCurrentRead->term bytes]; - - done = (memcmp (bufferEnd, seq, termlen) == 0); - } - } - - if(!done && theCurrentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if(theCurrentRead->bytesDone >= theCurrentRead->maxLength) - { - maxoutError = YES; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done when: - // - we reach maxLength (if there is a max) - // - all readable is read (see below) - - if (theCurrentRead->maxLength > 0) - { - done = (theCurrentRead->bytesDone >= theCurrentRead->maxLength); - } - } - } - } - - if (theCurrentRead->readLength <= 0 && theCurrentRead->term == nil) - { - // Read type #1 - read all available data - - if (theCurrentRead->bytesDone > 0) - { - // Ran out of bytes, so the "read-all-available-data" type packet is done - done = YES; - } - } - - if (done) - { - [self completeCurrentRead]; - if (!socketError) [self scheduleDequeueRead]; - } - else if (totalBytesRead > 0) - { - // We're not done with the readToLength or readToData yet, but we have read in some bytes - if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) - { - [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; - } - } - - if(socketError) - { - CFStreamError err = CFReadStreamGetError(theReadStream); - [self closeWithError:[self errorFromCFStreamError:err]]; - return; - } - - if(maxoutError) - { - [self closeWithError:[self getReadMaxedOutError]]; - return; - } -} - -// Ends current read and calls delegate. -- (void)completeCurrentRead -{ - NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read."); - - NSData *result; - - if (theCurrentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; - - result = theCurrentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([theCurrentRead->buffer length] > theCurrentRead->originalBufferLength) - { - NSUInteger readSize = theCurrentRead->startOffset + theCurrentRead->bytesDone; - NSUInteger origSize = theCurrentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); - - [theCurrentRead->buffer setLength:buffSize]; - } - - void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; - - result = [NSData dataWithBytesNoCopy:buffer length:theCurrentRead->bytesDone freeWhenDone:NO]; - } - - if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) - { - [theDelegate onSocket:self didReadData:result withTag:theCurrentRead->tag]; - } - - // Caller may have disconnected in the above delegate method - if (theCurrentRead != nil) - { - [self endCurrentRead]; - } -} - -// Ends current read. -- (void)endCurrentRead -{ - NSAssert(theCurrentRead, @"Trying to end current read when there is no current read."); - - [theReadTimer invalidate]; - theReadTimer = nil; - - theCurrentRead = nil; -} - -- (void)doReadTimeout:(NSTimer *)timer -{ - #pragma unused(timer) - - NSTimeInterval timeoutExtension = 0.0; - - if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag - elapsed:theCurrentRead->timeout - bytesDone:theCurrentRead->bytesDone]; - } - - if(timeoutExtension > 0.0) - { - theCurrentRead->timeout += timeoutExtension; - - theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension - target:self - selector:@selector(doReadTimeout:) - userInfo:nil - repeats:NO]; - [self runLoopAddTimer:theReadTimer]; - } - else - { - // Do not call endCurrentRead here. - // We must allow the delegate access to any partial read in the unreadData method. - - [self closeWithError:[self getReadTimeoutError]]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if (data == nil || [data length] == 0) return; - if (theFlags & kForbidReadsWrites) return; - - AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; - - [theWriteQueue addObject:packet]; - [self scheduleDequeueWrite]; - -} - -- (void)scheduleDequeueWrite -{ - if((theFlags & kDequeueWriteScheduled) == 0) - { - theFlags |= kDequeueWriteScheduled; - [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -/** - * Conditionally starts a new write. - * - * IF there is not another write in process - * AND there is a write queued - * AND we have a write stream available - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueWrite -{ - // Unset the flag indicating a call to this method is scheduled - theFlags &= ~kDequeueWriteScheduled; - - // If we're not currently processing a write AND we have an available write stream - if((theCurrentWrite == nil) && (theWriteStream != NULL)) - { - if([theWriteQueue count] > 0) - { - // Dequeue the next object in the write queue - theCurrentWrite = [theWriteQueue objectAtIndex:0]; - [theWriteQueue removeObjectAtIndex:0]; - - if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) - { - // Attempt to start TLS - theFlags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - // Start time-out timer - if(theCurrentWrite->timeout >= 0.0) - { - theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout - target:self - selector:@selector(doWriteTimeout:) - userInfo:nil - repeats:NO]; - [self runLoopAddTimer:theWriteTimer]; - } - - // Immediately write, if possible - [self doSendBytes]; - } - } - else if(theFlags & kDisconnectAfterWrites) - { - if(theFlags & kDisconnectAfterReads) - { - if(([theReadQueue count] == 0) && (theCurrentRead == nil)) - { - [self disconnect]; - } - } - else - { - [self disconnect]; - } - } - } -} - -/** - * Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes(). - * This method supports the kSocketCanAcceptBytes flag. -**/ -- (BOOL)canAcceptBytes -{ - if (theFlags & kSocketCanAcceptBytes) - { - return YES; - } - else - { - return CFWriteStreamCanAcceptBytes(theWriteStream); - } -} - -- (void)doSendBytes -{ - if ((theCurrentWrite == nil) || (theWriteStream == NULL)) - { - return; - } - - // Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet) - - NSUInteger totalBytesWritten = 0; - - BOOL done = NO; - BOOL error = NO; - - while (!done && !error && [self canAcceptBytes]) - { - // Figure out what to write - NSUInteger bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; - NSUInteger bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; - - UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); - - // Write - CFIndex result = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite); - - // Unset the "can accept bytes" flag - theFlags &= ~kSocketCanAcceptBytes; - - // Check results - if (result < 0) - { - error = YES; - } - else - { - CFIndex bytesWritten = result; - - // Update total amount read for the current write - theCurrentWrite->bytesDone += bytesWritten; - - // Update total amount written in this method invocation - totalBytesWritten += bytesWritten; - - // Is packet done? - done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); - } - } - - if(done) - { - [self completeCurrentWrite]; - [self scheduleDequeueWrite]; - } - else if(error) - { - CFStreamError err = CFWriteStreamGetError(theWriteStream); - [self closeWithError:[self errorFromCFStreamError:err]]; - return; - } - else if (totalBytesWritten > 0) - { - // We're not done with the entire write, but we have written some bytes - if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)]) - { - [theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag]; - } - } -} - -// Ends current write and calls delegate. -- (void)completeCurrentWrite -{ - NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); - - if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) - { - [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; - } - - if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. -} - -// Ends current write. -- (void)endCurrentWrite -{ - NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); - - [theWriteTimer invalidate]; - theWriteTimer = nil; - - theCurrentWrite = nil; -} - -- (void)doWriteTimeout:(NSTimer *)timer -{ - #pragma unused(timer) - - NSTimeInterval timeoutExtension = 0.0; - - if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag - elapsed:theCurrentWrite->timeout - bytesDone:theCurrentWrite->bytesDone]; - } - - if(timeoutExtension > 0.0) - { - theCurrentWrite->timeout += timeoutExtension; - - theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension - target:self - selector:@selector(doWriteTimeout:) - userInfo:nil - repeats:NO]; - [self runLoopAddTimer:theWriteTimer]; - } - else - { - [self closeWithError:[self getWriteTimeoutError]]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)startTLS:(NSDictionary *)tlsSettings -{ -#if DEBUG_THREAD_SAFETY - [self checkForThreadSafety]; -#endif - - if(tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. - - tlsSettings = [NSDictionary dictionary]; - } - - AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - - [theReadQueue addObject:packet]; - [self scheduleDequeueRead]; - - [theWriteQueue addObject:packet]; - [self scheduleDequeueWrite]; - -} - -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling StartTLS are complete - // - All queued writes prior to the user calling StartTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) - { - AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead; - - BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings, - (__bridge CFDictionaryRef)tlsPacket->tlsSettings); - BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings, - (__bridge CFDictionaryRef)tlsPacket->tlsSettings); - - if(!didStartOnReadStream || !didStartOnWriteStream) - { - [self closeWithError:[self getSocketError]]; - } - } -} - -- (void)onTLSHandshakeSuccessful -{ - if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) - { - theFlags &= ~kStartingReadTLS; - theFlags &= ~kStartingWriteTLS; - - if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)]) - { - [theDelegate onSocketDidSecure:self]; - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self scheduleDequeueRead]; - [self scheduleDequeueWrite]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CF Callbacks -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)doCFSocketCallback:(CFSocketCallBackType)type - forSocket:(CFSocketRef)sock - withAddress:(NSData *)address - withData:(const void *)pData -{ - #pragma unused(address) - - NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); - - switch (type) - { - case kCFSocketConnectCallBack: - // The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed. - if(pData) - [self doSocketOpen:sock withCFSocketError:kCFSocketError]; - else - [self doSocketOpen:sock withCFSocketError:kCFSocketSuccess]; - break; - case kCFSocketAcceptCallBack: - [self doAcceptFromSocket:sock withNewNativeSocket:*((CFSocketNativeHandle *)pData)]; - break; - default: - NSLog(@"AsyncSocket %p received unexpected CFSocketCallBackType %i", self, (int)type); - break; - } -} - -- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream -{ - #pragma unused(stream) - - NSParameterAssert(theReadStream != NULL); - - CFStreamError err; - switch (type) - { - case kCFStreamEventOpenCompleted: - theFlags |= kDidCompleteOpenForRead; - [self doStreamOpen]; - break; - case kCFStreamEventHasBytesAvailable: - if(theFlags & kStartingReadTLS) { - [self onTLSHandshakeSuccessful]; - } - else { - theFlags |= kSocketHasBytesAvailable; - [self doBytesAvailable]; - } - break; - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - err = CFReadStreamGetError (theReadStream); - [self closeWithError: [self errorFromCFStreamError:err]]; - break; - default: - NSLog(@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %i", self, (int)type); - } -} - -- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream -{ - #pragma unused(stream) - - NSParameterAssert(theWriteStream != NULL); - - CFStreamError err; - switch (type) - { - case kCFStreamEventOpenCompleted: - theFlags |= kDidCompleteOpenForWrite; - [self doStreamOpen]; - break; - case kCFStreamEventCanAcceptBytes: - if(theFlags & kStartingWriteTLS) { - [self onTLSHandshakeSuccessful]; - } - else { - theFlags |= kSocketCanAcceptBytes; - [self doSendBytes]; - } - break; - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - err = CFWriteStreamGetError (theWriteStream); - [self closeWithError: [self errorFromCFStreamError:err]]; - break; - default: - NSLog(@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %i", self, (int)type); - } -} - -/** - * This is the callback we setup for CFSocket. - * This method does nothing but forward the call to it's Objective-C counterpart -**/ -static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef inAddress, const void *pData, void *pInfo) -{ - @autoreleasepool { - - AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; - NSData *address = [(__bridge NSData *)inAddress copy]; - - [theSocket doCFSocketCallback:type forSocket:sref withAddress:address withData:pData]; - - } -} - -/** - * This is the callback we setup for CFReadStream. - * This method does nothing but forward the call to it's Objective-C counterpart -**/ -static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) -{ - @autoreleasepool { - - AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; - [theSocket doCFReadStreamCallback:type forStream:stream]; - - } -} - -/** - * This is the callback we setup for CFWriteStream. - * This method does nothing but forward the call to it's Objective-C counterpart -**/ -static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - @autoreleasepool { - - AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; - [theSocket doCFWriteStreamCallback:type forStream:stream]; - - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Methods -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Return line separators. -+ (NSData *)CRLFData -{ - return [NSData dataWithBytes:"\x0D\x0A" length:2]; -} - -+ (NSData *)CRData -{ - return [NSData dataWithBytes:"\x0D" length:1]; -} - -+ (NSData *)LFData -{ - return [NSData dataWithBytes:"\x0A" length:1]; -} - -+ (NSData *)ZeroData -{ - return [NSData dataWithBytes:"" length:1]; -} - -@end diff --git a/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h b/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h deleted file mode 100644 index 9099488..0000000 --- a/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h +++ /dev/null @@ -1,370 +0,0 @@ -// -// AsyncUdpSocket.h -// -// This class is in the public domain. -// Originally created by Robbie Hanson on Wed Oct 01 2008. -// Updated and maintained by Deusty Designs and the Mac development community. -// -// http://code.google.com/p/cocoaasyncsocket/ -// - -#import - -@class AsyncSendPacket; -@class AsyncReceivePacket; - -extern NSString *const AsyncUdpSocketException; -extern NSString *const AsyncUdpSocketErrorDomain; - -enum AsyncUdpSocketError -{ - AsyncUdpSocketCFSocketError = kCFSocketError, // From CFSocketError enum - AsyncUdpSocketNoError = 0, // Never used - AsyncUdpSocketBadParameter, // Used if given a bad parameter (such as an improper address) - AsyncUdpSocketIPv4Unavailable, // Used if you bind/connect using IPv6 only - AsyncUdpSocketIPv6Unavailable, // Used if you bind/connect using IPv4 only (or iPhone) - AsyncUdpSocketSendTimeoutError, - AsyncUdpSocketReceiveTimeoutError -}; -typedef enum AsyncUdpSocketError AsyncUdpSocketError; - -@interface AsyncUdpSocket : NSObject -{ - CFSocketRef theSocket4; // IPv4 socket - CFSocketRef theSocket6; // IPv6 socket - - CFRunLoopSourceRef theSource4; // For theSocket4 - CFRunLoopSourceRef theSource6; // For theSocket6 - CFRunLoopRef theRunLoop; - CFSocketContext theContext; - NSArray *theRunLoopModes; - - NSMutableArray *theSendQueue; - AsyncSendPacket *theCurrentSend; - NSTimer *theSendTimer; - - NSMutableArray *theReceiveQueue; - AsyncReceivePacket *theCurrentReceive; - NSTimer *theReceiveTimer; - - id theDelegate; - UInt16 theFlags; - - long theUserData; - - NSString *cachedLocalHost; - UInt16 cachedLocalPort; - - NSString *cachedConnectedHost; - UInt16 cachedConnectedPort; - - UInt32 maxReceiveBufferSize; -} - -/** - * Creates new instances of AsyncUdpSocket. -**/ -- (id)init; -- (id)initWithDelegate:(id)delegate; -- (id)initWithDelegate:(id)delegate userData:(long)userData; - -/** - * Creates new instances of AsyncUdpSocket that support only IPv4 or IPv6. - * The other init methods will support both, unless specifically binded or connected to one protocol. - * If you know you'll only be using one protocol, these init methods may be a bit more efficient. -**/ -- (id)initIPv4; -- (id)initIPv6; - -- (id)delegate; -- (void)setDelegate:(id)delegate; - -- (long)userData; -- (void)setUserData:(long)userData; - -/** - * Returns the local address info for the socket. - * - * Note: Address info may not be available until after the socket has been bind'ed, - * or until after data has been sent. -**/ -- (NSString *)localHost; -- (UInt16)localPort; - -/** - * Returns the remote address info for the socket. - * - * Note: Since UDP is connectionless by design, connected address info - * will not be available unless the socket is explicitly connected to a remote host/port -**/ -- (NSString *)connectedHost; -- (UInt16)connectedPort; - -/** - * Returns whether or not this socket has been connected to a single host. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * If connected, the socket will only be able to send/receive data to/from the connected host. -**/ -- (BOOL)isConnected; - -/** - * Returns whether or not this socket has been closed. - * The only way a socket can be closed is if you explicitly call one of the close methods. -**/ -- (BOOL)isClosed; - -/** - * Returns whether or not this socket supports IPv4. - * By default this will be true, unless the socket is specifically initialized as IPv6 only, - * or is binded or connected to an IPv6 address. -**/ -- (BOOL)isIPv4; - -/** - * Returns whether or not this socket supports IPv6. - * By default this will be true, unless the socket is specifically initialized as IPv4 only, - * or is binded or connected to an IPv4 address. - * - * This method will also return false on platforms that do not support IPv6. - * Note: The iPhone does not currently support IPv6. -**/ -- (BOOL)isIPv6; - -/** - * Returns the mtu of the socket. - * If unknown, returns zero. - * - * Sending data larger than this may result in an error. - * This is an advanced topic, and one should understand the wide range of mtu's on networks and the internet. - * Therefore this method is only for reference and may be of little use in many situations. -**/ -- (unsigned int)maximumTransmissionUnit; - -/** - * Binds the UDP socket to the given port and optional address. - * Binding should be done for server sockets that receive data prior to sending it. - * Client sockets can skip binding, - * as the OS will automatically assign the socket an available port when it starts sending data. - * - * You cannot bind a socket after its been connected. - * You can only bind a socket once. - * You can still connect a socket (if desired) after binding. - * - * 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)bindToPort:(UInt16)port error:(NSError **)errPtr; -- (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **)errPtr; - -/** - * Connects the UDP socket to the given host and port. - * By design, UDP is a connectionless protocol, and connecting is not needed. - * - * Choosing to connect to a specific host/port has the following effect: - * - You will only be able to send data to the connected host/port. - * - You will only be able to receive data from the connected host/port. - * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". - * - * Connecting a UDP socket does not result in any communication on the socket. - * It simply changes the internal state of the socket. - * - * You cannot bind a socket after its been connected. - * You can only connect a socket once. - * - * 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)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr; -- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; - -/** - * Join multicast group - * - * Group should be an IP address (eg @"225.228.0.1") -**/ -- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)interface error:(NSError **)errPtr; - -/** - * By default, the underlying socket in the OS will not allow you to send broadcast messages. - * In order to send broadcast messages, you need to enable this functionality in the socket. - * - * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is - * delivered to every host on the network. - * The reason this is generally disabled by default is to prevent - * accidental broadcast messages from flooding the network. -**/ -- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; - -/** - * Asynchronously sends the given data, with the given timeout and tag. - * - * This method may only be used with a connected socket. - * - * If data is nil or zero-length, this method does nothing and immediately returns NO. - * If the socket is not connected, this method does nothing and immediately returns NO. -**/ -- (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. - * - * This method cannot be used with a connected socket. - * - * If data is nil or zero-length, this method does nothing and immediately returns NO. - * If the socket is connected, this method does nothing and immediately returns NO. - * If unable to resolve host to a valid IPv4 or IPv6 address, this method returns NO. -**/ -- (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Asynchronously sends the given data, with the given timeout and tag, to the given address. - * - * This method cannot be used with a connected socket. - * - * If data is nil or zero-length, this method does nothing and immediately returns NO. - * If the socket is connected, this method does nothing and immediately returns NO. -**/ -- (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Asynchronously receives a single datagram packet. - * - * If the receive succeeds, the onUdpSocket:didReceiveData:fromHost:port:tag delegate method will be called. - * Otherwise, a timeout will occur, and the onUdpSocket:didNotReceiveDataWithTag: delegate method will be called. -**/ -- (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag; - -/** - * Closes the socket immediately. Any pending send or receive operations are dropped. -**/ -- (void)close; - -/** - * Closes after all pending send operations have completed. - * After calling this, the sendData: and receive: methods will do nothing. - * In other words, you won't be able to add any more send or receive operations to the queue. - * The socket will close even if there are still pending receive operations. -**/ -- (void)closeAfterSending; - -/** - * Closes after all pending receive operations have completed. - * After calling this, the sendData: and receive: methods will do nothing. - * In other words, you won't be able to add any more send or receive operations to the queue. - * The socket will close even if there are still pending send operations. -**/ -- (void)closeAfterReceiving; - -/** - * Closes after all pending send and receive operations have completed. - * After calling this, the sendData: and receive: methods will do nothing. - * In other words, you won't be able to add any more send or receive operations to the queue. -**/ -- (void)closeAfterSendingAndReceiving; - -/** - * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. - * The default size is 9216 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. - * - * In practice, however, the size of UDP packets will be much smaller. - * Indeed most protocols will send and receive packets of only a few bytes, - * or will set a limit on the size of packets to prevent fragmentation in the IP layer. - * - * If you set the buffer size too small, the sockets API in the OS will silently discard - * any extra data, and you will not be notified of the error. -**/ -- (UInt32)maxReceiveBufferSize; -- (void)setMaxReceiveBufferSize:(UInt32)max; - -/** - * When you create an AsyncUdpSocket, it is added to the runloop of the current thread. - * So it is easiest to simply create the socket on the thread you intend to use it. - * - * If, however, you need to move the socket to a separate thread at a later time, this - * method may be used to accomplish the task. - * - * This method must be called from the thread/runloop the socket is currently running on. - * - * Note: After calling this method, all further method calls to this object should be done from the given runloop. - * Also, all delegate calls will be sent on the given runloop. -**/ -- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; - -/** - * Allows you to configure which run loop modes the socket uses. - * The default set of run loop modes is NSDefaultRunLoopMode. - * - * If you'd like your socket to continue operation during other modes, you may want to add modes such as - * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. - * - * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. -**/ -- (BOOL)setRunLoopModes:(NSArray *)runLoopModes; - -/** - * Returns the current run loop modes the AsyncSocket instance is operating in. - * The default set of run loop modes is NSDefaultRunLoopMode. -**/ -- (NSArray *)runLoopModes; - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@protocol AsyncUdpSocketDelegate -@optional - -/** - * Called when the datagram with the given tag has been sent. -**/ -- (void)onUdpSocket:(AsyncUdpSocket *)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)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error; - -/** - * Called when the socket has received the requested datagram. - * - * Due to the nature of UDP, you may occasionally receive undesired packets. - * These may be rogue UDP packets from unknown hosts, - * or they may be delayed packets arriving after retransmissions have already occurred. - * It's important these packets are properly ignored, while not interfering with the flow of your implementation. - * As an aid, this delegate method has a boolean return value. - * If you ever need to ignore a received packet, simply return NO, - * and AsyncUdpSocket will continue as if the packet never arrived. - * That is, the original receive request will still be queued, and will still timeout as usual if a timeout was set. - * For example, say you requested to receive data, and you set a timeout of 500 milliseconds, using a tag of 15. - * If rogue data arrives after 250 milliseconds, this delegate method would be invoked, and you could simply return NO. - * If the expected data then arrives within the next 250 milliseconds, - * this delegate method will be invoked, with a tag of 15, just as if the rogue data never appeared. - * - * Under normal circumstances, you simply return YES from this method. -**/ -- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock - didReceiveData:(NSData *)data - withTag:(long)tag - fromHost:(NSString *)host - port:(UInt16)port; - -/** - * Called if an error occurs while trying to receive a requested datagram. - * This is generally due to a timeout, but could potentially be something else if some kind of OS error occurred. -**/ -- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error; - -/** - * Called when the socket is closed. - * A socket is only closed if you explicitly call one of the close methods. -**/ -- (void)onUdpSocketDidClose:(AsyncUdpSocket *)sock; - -@end diff --git a/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m b/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m deleted file mode 100644 index d898d3b..0000000 --- a/Pods/CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m +++ /dev/null @@ -1,2297 +0,0 @@ -// -// AsyncUdpSocket.m -// -// This class is in the public domain. -// Originally created by Robbie Hanson on Wed Oct 01 2008. -// Updated and maintained by Deusty Designs and the Mac development community. -// -// http://code.google.com/p/cocoaasyncsocket/ -// - -#if ! __has_feature(objc_arc) -#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). -#endif - -#import "AsyncUdpSocket.h" -#import -#import -#import -#import -#import -#import - -#if TARGET_OS_IPHONE -// Note: You may need to add the CFNetwork Framework to your project -#import -#endif - - -#define SENDQUEUE_CAPACITY 5 // Initial capacity -#define RECEIVEQUEUE_CAPACITY 5 // Initial capacity - -#define DEFAULT_MAX_RECEIVE_BUFFER_SIZE 9216 - -NSString *const AsyncUdpSocketException = @"AsyncUdpSocketException"; -NSString *const AsyncUdpSocketErrorDomain = @"AsyncUdpSocketErrorDomain"; - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 -// Mutex lock used by all instances of AsyncUdpSocket, to protect getaddrinfo. -// Prior to Mac OS X 10.5 this method was not thread-safe. -static NSString *getaddrinfoLock = @"lock"; -#endif - -enum AsyncUdpSocketFlags -{ - kDidBind = 1 << 0, // If set, bind has been called. - kDidConnect = 1 << 1, // If set, connect has been called. - kSock4CanAcceptBytes = 1 << 2, // If set, we know socket4 can accept bytes. If unset, it's unknown. - kSock6CanAcceptBytes = 1 << 3, // If set, we know socket6 can accept bytes. If unset, it's unknown. - kSock4HasBytesAvailable = 1 << 4, // If set, we know socket4 has bytes available. If unset, it's unknown. - kSock6HasBytesAvailable = 1 << 5, // If set, we know socket6 has bytes available. If unset, it's unknown. - kForbidSendReceive = 1 << 6, // If set, no new send or receive operations are allowed to be queued. - kCloseAfterSends = 1 << 7, // If set, close as soon as no more sends are queued. - kCloseAfterReceives = 1 << 8, // If set, close as soon as no more receives are queued. - kDidClose = 1 << 9, // If set, the socket has been closed, and should not be used anymore. - kDequeueSendScheduled = 1 << 10, // If set, a maybeDequeueSend operation is already scheduled. - kDequeueReceiveScheduled = 1 << 11, // If set, a maybeDequeueReceive operation is already scheduled. - kFlipFlop = 1 << 12, // Used to alternate between IPv4 and IPv6 sockets. -}; - -@interface AsyncUdpSocket (Private) - -// Run Loop -- (void)runLoopAddSource:(CFRunLoopSourceRef)source; -- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; -- (void)runLoopAddTimer:(NSTimer *)timer; -- (void)runLoopRemoveTimer:(NSTimer *)timer; - -// Utilities -- (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4; -- (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6; -- (NSString *)addressHost:(struct sockaddr *)pSockaddr; - -// Disconnect Implementation -- (void)emptyQueues; -- (void)closeSocket4; -- (void)closeSocket6; -- (void)maybeScheduleClose; - -// Errors -- (NSError *)getErrnoError; -- (NSError *)getSocketError; -- (NSError *)getIPv4UnavailableError; -- (NSError *)getIPv6UnavailableError; -- (NSError *)getSendTimeoutError; -- (NSError *)getReceiveTimeoutError; - -// Diagnostics -- (NSString *)connectedHost:(CFSocketRef)socket; -- (UInt16)connectedPort:(CFSocketRef)socket; -- (NSString *)localHost:(CFSocketRef)socket; -- (UInt16)localPort:(CFSocketRef)socket; - -// Sending -- (BOOL)canAcceptBytes:(CFSocketRef)sockRef; -- (void)scheduleDequeueSend; -- (void)maybeDequeueSend; -- (void)doSend:(CFSocketRef)sockRef; -- (void)completeCurrentSend; -- (void)failCurrentSend:(NSError *)error; -- (void)endCurrentSend; -- (void)doSendTimeout:(NSTimer *)timer; - -// Receiving -- (BOOL)hasBytesAvailable:(CFSocketRef)sockRef; -- (void)scheduleDequeueReceive; -- (void)maybeDequeueReceive; -- (void)doReceive4; -- (void)doReceive6; -- (void)doReceive:(CFSocketRef)sockRef; -- (BOOL)maybeCompleteCurrentReceive; -- (void)failCurrentReceive:(NSError *)error; -- (void)endCurrentReceive; -- (void)doReceiveTimeout:(NSTimer *)timer; - -@end - -static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The AsyncSendPacket encompasses the instructions for a single send/write. -**/ -@interface AsyncSendPacket : NSObject -{ -@public - NSData *buffer; - NSData *address; - NSTimeInterval timeout; - long tag; -} -- (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation AsyncSendPacket - -- (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - buffer = d; - address = a; - timeout = t; - tag = i; - } - return self; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The AsyncReceivePacket encompasses the instructions for a single receive/read. -**/ -@interface AsyncReceivePacket : NSObject -{ -@public - NSTimeInterval timeout; - long tag; - NSData *buffer; - NSString *host; - UInt16 port; -} -- (id)initWithTimeout:(NSTimeInterval)t tag:(long)i; -@end - -@implementation AsyncReceivePacket - -- (id)initWithTimeout:(NSTimeInterval)t tag:(long)i -{ - if((self = [super init])) - { - timeout = t; - tag = i; - - buffer = nil; - host = nil; - port = 0; - } - return self; -} - -@end - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@implementation AsyncUdpSocket - -- (id)initWithDelegate:(id)delegate userData:(long)userData enableIPv4:(BOOL)enableIPv4 enableIPv6:(BOOL)enableIPv6 -{ - if((self = [super init])) - { - theFlags = 0; - theDelegate = delegate; - theUserData = userData; - maxReceiveBufferSize = DEFAULT_MAX_RECEIVE_BUFFER_SIZE; - - theSendQueue = [[NSMutableArray alloc] initWithCapacity:SENDQUEUE_CAPACITY]; - theCurrentSend = nil; - theSendTimer = nil; - - theReceiveQueue = [[NSMutableArray alloc] initWithCapacity:RECEIVEQUEUE_CAPACITY]; - theCurrentReceive = nil; - theReceiveTimer = nil; - - // Socket context - theContext.version = 0; - theContext.info = (__bridge void *)self; - theContext.retain = nil; - theContext.release = nil; - theContext.copyDescription = nil; - - // Create the sockets - theSocket4 = NULL; - theSocket6 = NULL; - - if(enableIPv4) - { - theSocket4 = CFSocketCreate(kCFAllocatorDefault, - PF_INET, - SOCK_DGRAM, - IPPROTO_UDP, - kCFSocketReadCallBack | kCFSocketWriteCallBack, - (CFSocketCallBack)&MyCFSocketCallback, - &theContext); - } - if(enableIPv6) - { - theSocket6 = CFSocketCreate(kCFAllocatorDefault, - PF_INET6, - SOCK_DGRAM, - IPPROTO_UDP, - kCFSocketReadCallBack | kCFSocketWriteCallBack, - (CFSocketCallBack)&MyCFSocketCallback, - &theContext); - } - - // Disable continuous callbacks for read and write. - // If we don't do this, the socket(s) will just sit there firing read callbacks - // at us hundreds of times a second if we don't immediately read the available data. - if(theSocket4) - { - CFSocketSetSocketFlags(theSocket4, kCFSocketCloseOnInvalidate); - } - if(theSocket6) - { - CFSocketSetSocketFlags(theSocket6, kCFSocketCloseOnInvalidate); - } - - // Get the CFRunLoop to which the socket should be attached. - theRunLoop = CFRunLoopGetCurrent(); - - // Set default run loop modes - theRunLoopModes = [NSArray arrayWithObject:NSDefaultRunLoopMode]; - - // Attach the sockets to the run loop - - if(theSocket4) - { - theSource4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket4, 0); - [self runLoopAddSource:theSource4]; - } - - if(theSocket6) - { - theSource6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket6, 0); - [self runLoopAddSource:theSource6]; - } - - cachedLocalPort = 0; - cachedConnectedPort = 0; - } - return self; -} - -- (id)init -{ - return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:YES]; -} - -- (id)initWithDelegate:(id)delegate -{ - return [self initWithDelegate:delegate userData:0 enableIPv4:YES enableIPv6:YES]; -} - -- (id)initWithDelegate:(id)delegate userData:(long)userData -{ - return [self initWithDelegate:delegate userData:userData enableIPv4:YES enableIPv6:YES]; -} - -- (id)initIPv4 -{ - return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:NO]; -} - -- (id)initIPv6 -{ - return [self initWithDelegate:nil userData:0 enableIPv4:NO enableIPv6:YES]; -} - -- (void) dealloc -{ - [self close]; - - [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onUdpSocketDidClose:) object:self]; - [NSObject cancelPreviousPerformRequestsWithTarget:self]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Accessors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (id)delegate -{ - return theDelegate; -} - -- (void)setDelegate:(id)delegate -{ - theDelegate = delegate; -} - -- (long)userData -{ - return theUserData; -} - -- (void)setUserData:(long)userData -{ - theUserData = userData; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Run Loop -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)runLoopAddSource:(CFRunLoopSourceRef)source -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopAddTimer:(NSTimer *)timer -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); - } -} - -- (void)runLoopRemoveTimer:(NSTimer *)timer -{ - for (NSString *runLoopMode in theRunLoopModes) - { - CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Configuration -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (UInt32)maxReceiveBufferSize -{ - return maxReceiveBufferSize; -} - -- (void)setMaxReceiveBufferSize:(UInt32)max -{ - maxReceiveBufferSize = max; -} - -/** - * See the header file for a full explanation of this method. -**/ -- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"moveToRunLoop must be called from within the current RunLoop!"); - - if(runLoop == nil) - { - return NO; - } - if(theRunLoop == [runLoop getCFRunLoop]) - { - return YES; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueSendScheduled; - theFlags &= ~kDequeueReceiveScheduled; - - if(theSource4) [self runLoopRemoveSource:theSource4]; - if(theSource6) [self runLoopRemoveSource:theSource6]; - - if(theSendTimer) [self runLoopRemoveTimer:theSendTimer]; - if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer]; - - theRunLoop = [runLoop getCFRunLoop]; - - if(theSendTimer) [self runLoopAddTimer:theSendTimer]; - if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer]; - - if(theSource4) [self runLoopAddSource:theSource4]; - if(theSource6) [self runLoopAddSource:theSource6]; - - [runLoop performSelector:@selector(maybeDequeueSend) target:self argument:nil order:0 modes:theRunLoopModes]; - [runLoop performSelector:@selector(maybeDequeueReceive) target:self argument:nil order:0 modes:theRunLoopModes]; - [runLoop performSelector:@selector(maybeScheduleClose) target:self argument:nil order:0 modes:theRunLoopModes]; - - return YES; -} - -/** - * See the header file for a full explanation of this method. -**/ -- (BOOL)setRunLoopModes:(NSArray *)runLoopModes -{ - NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), - @"setRunLoopModes must be called from within the current RunLoop!"); - - if([runLoopModes count] == 0) - { - return NO; - } - if([theRunLoopModes isEqualToArray:runLoopModes]) - { - return YES; - } - - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - theFlags &= ~kDequeueSendScheduled; - theFlags &= ~kDequeueReceiveScheduled; - - if(theSource4) [self runLoopRemoveSource:theSource4]; - if(theSource6) [self runLoopRemoveSource:theSource6]; - - if(theSendTimer) [self runLoopRemoveTimer:theSendTimer]; - if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer]; - - theRunLoopModes = [runLoopModes copy]; - - if(theSendTimer) [self runLoopAddTimer:theSendTimer]; - if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer]; - - if(theSource4) [self runLoopAddSource:theSource4]; - if(theSource6) [self runLoopAddSource:theSource6]; - - [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - [self performSelector:@selector(maybeScheduleClose) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - - return YES; -} - -- (NSArray *)runLoopModes -{ - return [theRunLoopModes copy]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Utilities: -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure. - * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6. - * - * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo). -**/ -- (int)convertForBindHost:(NSString *)host - port:(UInt16)port - intoAddress4:(NSData **)address4 - address6:(NSData **)address6 -{ - if(host == nil || ([host length] == 0)) - { - // Use ANY 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_ANY); - memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.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_any; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures for CFSocketSetAddress. - if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; - if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - return 0; - } - else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Note: getaddrinfo("localhost",...) fails on 10.5.3 - - // 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)); - - 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 for CFSocketSetAddress. - if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; - if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - return 0; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - @synchronized (getaddrinfoLock) -#endif - { - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_PASSIVE; - - int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if(error) return error; - - for(res = res0; res; res = res->ai_next) - { - if(address4 && !*address4 && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structures for CFSocketSetAddress. - if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if(address6 && !*address6 && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structures for CFSocketSetAddress. - if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - - return 0; - } -} - -/** - * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure. - * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6. - * - * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo). -**/ -- (int)convertForSendHost:(NSString *)host - port:(UInt16)port - intoAddress4:(NSData **)address4 - address6:(NSData **)address6 -{ - if(host == nil || ([host length] == 0)) - { - // We're not binding, so what are we supposed to do with this? - return EAI_NONAME; - } - else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Note: getaddrinfo("localhost",...) fails on 10.5.3 - - // 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)); - - 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 for CFSocketSetAddress. - if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; - if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - return 0; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 - @synchronized (getaddrinfoLock) -#endif - { - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - // No passive flag on a send or connect - - int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if(error) return error; - - for(res = res0; res; res = res->ai_next) - { - if(address4 && !*address4 && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structures for CFSocketSetAddress. - if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if(address6 && !*address6 && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structures for CFSocketSetAddress. - if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - - return 0; - } -} - -- (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4 -{ - char addrBuf[INET_ADDRSTRLEN]; - - if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, sizeof(addrBuf)) == NULL) - { - [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."]; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -- (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6 -{ - char addrBuf[INET6_ADDRSTRLEN]; - - if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, sizeof(addrBuf)) == NULL) - { - [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."]; - } - - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; -} - -- (NSString *)addressHost:(struct sockaddr *)pSockaddr -{ - if(pSockaddr->sa_family == AF_INET) - { - return [self addressHost4:(struct sockaddr_in *)pSockaddr]; - } - else - { - return [self addressHost6:(struct sockaddr_in6 *)pSockaddr]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Socket Implementation: -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Binds the underlying socket(s) to the given port. - * The socket(s) will be able to receive data on any interface. - * - * 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)bindToPort:(UInt16)port error:(NSError **)errPtr -{ - return [self bindToAddress:nil port:port error:errPtr]; -} - -/** - * Binds the underlying socket(s) to the given address and port. - * The sockets(s) will be able to receive data only on the given interface. - * - * To receive data on any interface, pass nil or "". - * To receive data only on the loopback interface, pass "localhost" or "loopback". - * - * 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)bindToAddress:(NSString *)host port:(UInt16)port error:(NSError **)errPtr -{ - if(theFlags & kDidClose) - { - [NSException raise:AsyncUdpSocketException - format:@"The socket is closed."]; - } - if(theFlags & kDidBind) - { - [NSException raise:AsyncUdpSocketException - format:@"Cannot bind a socket more than once."]; - } - if(theFlags & kDidConnect) - { - [NSException raise:AsyncUdpSocketException - format:@"Cannot bind after connecting. If needed, bind first, then connect."]; - } - - // Convert the given host/port into native address structures for CFSocketSetAddress - NSData *address4 = nil, *address6 = nil; - - int gai_error = [self convertForBindHost:host port:port intoAddress4:&address4 address6:&address6]; - if(gai_error) - { - if(errPtr) - { - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:info]; - } - return NO; - } - - NSAssert((address4 || address6), @"address4 and address6 are nil"); - - // Set the SO_REUSEADDR flags - - int reuseOn = 1; - if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - - // Bind the sockets - - if(address4) - { - if(theSocket4) - { - CFSocketError error = CFSocketSetAddress(theSocket4, (__bridge CFDataRef)address4); - if(error != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - - if(!address6) - { - // Using IPv4 only - [self closeSocket6]; - } - } - else if(!address6) - { - if(errPtr) *errPtr = [self getIPv4UnavailableError]; - return NO; - } - } - - if(address6) - { - // Note: The iPhone doesn't currently support IPv6 - - if(theSocket6) - { - CFSocketError error = CFSocketSetAddress(theSocket6, (__bridge CFDataRef)address6); - if(error != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - - if(!address4) - { - // Using IPv6 only - [self closeSocket4]; - } - } - else if(!address4) - { - if(errPtr) *errPtr = [self getIPv6UnavailableError]; - return NO; - } - } - - theFlags |= kDidBind; - return YES; -} - -/** - * Connects the underlying UDP socket to the given host and port. - * If an IPv4 address is resolved, the IPv4 socket is connected, and the IPv6 socket is invalidated and released. - * If an IPv6 address is resolved, the IPv6 socket is connected, and the IPv4 socket is invalidated and released. - * - * 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)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr -{ - if(theFlags & kDidClose) - { - [NSException raise:AsyncUdpSocketException - format:@"The socket is closed."]; - } - if(theFlags & kDidConnect) - { - [NSException raise:AsyncUdpSocketException - format:@"Cannot connect a socket more than once."]; - } - - // Convert the given host/port into native address structures for CFSocketSetAddress - NSData *address4 = nil, *address6 = nil; - - int error = [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6]; - if(error) - { - if(errPtr) - { - NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; - } - return NO; - } - - NSAssert((address4 || address6), @"address4 and address6 are nil"); - - // We only want to connect via a single interface. - // IPv4 is currently preferred, but this may change in the future. - - CFSocketError sockErr; - - if (address4) - { - if (theSocket4) - { - sockErr = CFSocketConnectToAddress(theSocket4, (__bridge CFDataRef)address4, (CFTimeInterval)0.0); - if (sockErr != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - theFlags |= kDidConnect; - - // We're connected to an IPv4 address, so no need for the IPv6 socket - [self closeSocket6]; - - return YES; - } - else if(!address6) - { - if(errPtr) *errPtr = [self getIPv4UnavailableError]; - return NO; - } - } - - if (address6) - { - // Note: The iPhone doesn't currently support IPv6 - - if (theSocket6) - { - sockErr = CFSocketConnectToAddress(theSocket6, (__bridge CFDataRef)address6, (CFTimeInterval)0.0); - if (sockErr != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - theFlags |= kDidConnect; - - // We're connected to an IPv6 address, so no need for the IPv4 socket - [self closeSocket4]; - - return YES; - } - else - { - if(errPtr) *errPtr = [self getIPv6UnavailableError]; - return NO; - } - } - - // It shouldn't be possible to get to this point because either address4 or address6 was non-nil. - if(errPtr) *errPtr = nil; - return NO; -} - -/** - * Connects the underlying UDP socket to the remote address. - * If the address is an IPv4 address, the IPv4 socket is connected, and the IPv6 socket is invalidated and released. - * If the address is an IPv6 address, the IPv6 socket is connected, and the IPv4 socket is invalidated and released. - * - * The address is a native address structure, as may be returned from API's such as Bonjour. - * An address may be created manually by simply wrapping a sockaddr_in or sockaddr_in6 in an NSData object. - * - * 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)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr -{ - if (theFlags & kDidClose) - { - [NSException raise:AsyncUdpSocketException - format:@"The socket is closed."]; - } - if (theFlags & kDidConnect) - { - [NSException raise:AsyncUdpSocketException - format:@"Cannot connect a socket more than once."]; - } - - CFSocketError sockErr; - - // Is remoteAddr an IPv4 address? - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - if (theSocket4) - { - sockErr = CFSocketConnectToAddress(theSocket4, (__bridge CFDataRef)remoteAddr, (CFTimeInterval)0.0); - if (sockErr != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - theFlags |= kDidConnect; - - // We're connected to an IPv4 address, so no need for the IPv6 socket - [self closeSocket6]; - - return YES; - } - else - { - if(errPtr) *errPtr = [self getIPv4UnavailableError]; - return NO; - } - } - - // Is remoteAddr an IPv6 address? - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - if (theSocket6) - { - sockErr = CFSocketConnectToAddress(theSocket6, (__bridge CFDataRef)remoteAddr, (CFTimeInterval)0.0); - if (sockErr != kCFSocketSuccess) - { - if(errPtr) *errPtr = [self getSocketError]; - return NO; - } - theFlags |= kDidConnect; - - // We're connected to an IPv6 address, so no need for the IPv4 socket - [self closeSocket4]; - - return YES; - } - else - { - if(errPtr) *errPtr = [self getIPv6UnavailableError]; - return NO; - } - } - - // The remoteAddr was invalid - if(errPtr) - { - NSString *errMsg = @"remoteAddr parameter is not a valid address"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain - code:AsyncUdpSocketBadParameter - userInfo:info]; - } - return NO; -} - -/** - * Join multicast group - * - * Group should be a multicast IP address (eg. @"239.255.250.250" for IPv4). - * Address is local interface for IPv4, but currently defaults under IPv6. -**/ -- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr -{ - return [self joinMulticastGroup:group withAddress:nil error:errPtr]; -} - -- (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)address error:(NSError **)errPtr -{ - if(theFlags & kDidClose) - { - [NSException raise:AsyncUdpSocketException - format:@"The socket is closed."]; - } - if(!(theFlags & kDidBind)) - { - [NSException raise:AsyncUdpSocketException - format:@"Must bind a socket before joining a multicast group."]; - } - if(theFlags & kDidConnect) - { - [NSException raise:AsyncUdpSocketException - format:@"Cannot join a multicast group if connected."]; - } - - // Get local interface address - // Convert the given host/port into native address structures for CFSocketSetAddress - NSData *address4 = nil, *address6 = nil; - - int error = [self convertForBindHost:address port:0 intoAddress4:&address4 address6:&address6]; - if(error) - { - if(errPtr) - { - NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; - NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'address': %@", errMsg]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; - } - return NO; - } - - NSAssert((address4 || address6), @"address4 and address6 are nil"); - - // Get multicast address (group) - NSData *group4 = nil, *group6 = nil; - - error = [self convertForBindHost:group port:0 intoAddress4:&group4 address6:&group6]; - if(error) - { - if(errPtr) - { - NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; - NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'group': %@", errMsg]; - NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; - } - return NO; - } - - NSAssert((group4 || group6), @"group4 and group6 are nil"); - - if(theSocket4 && group4 && address4) - { - const struct sockaddr_in* nativeAddress = [address4 bytes]; - const struct sockaddr_in* nativeGroup = [group4 bytes]; - - struct ip_mreq imreq; - imreq.imr_multiaddr = nativeGroup->sin_addr; - imreq.imr_interface = nativeAddress->sin_addr; - - // JOIN multicast group on default interface - error = setsockopt(CFSocketGetNative(theSocket4), IPPROTO_IP, IP_ADD_MEMBERSHIP, - (const void *)&imreq, sizeof(struct ip_mreq)); - if(error) - { - if(errPtr) - { - NSString *errMsg = @"Unable to join IPv4 multicast group"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; - } - return NO; - } - - // Using IPv4 only - [self closeSocket6]; - - return YES; - } - - if(theSocket6 && group6 && address6) - { - const struct sockaddr_in6* nativeGroup = [group6 bytes]; - - struct ipv6_mreq imreq; - imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; - imreq.ipv6mr_interface = 0; - - // JOIN multicast group on default interface - error = setsockopt(CFSocketGetNative(theSocket6), IPPROTO_IP, IPV6_JOIN_GROUP, - (const void *)&imreq, sizeof(struct ipv6_mreq)); - if(error) - { - if(errPtr) - { - NSString *errMsg = @"Unable to join IPv6 multicast group"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; - } - return NO; - } - - // Using IPv6 only - [self closeSocket4]; - - return YES; - } - - // The given address and group didn't match the existing socket(s). - // This means there were no compatible combination of all IPv4 or IPv6 socket, group and address. - if(errPtr) - { - NSString *errMsg = @"Invalid group and/or address, not matching existing socket(s)"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain - code:AsyncUdpSocketBadParameter - userInfo:info]; - } - return NO; -} - -/** - * By default, the underlying socket in the OS will not allow you to send broadcast messages. - * In order to send broadcast messages, you need to enable this functionality in the socket. - * - * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is - * delivered to every host on the network. - * The reason this is generally disabled by default is to prevent - * accidental broadcast messages from flooding the network. -**/ -- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr -{ - if (theSocket4) - { - int value = flag ? 1 : 0; - int error = setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_BROADCAST, - (const void *)&value, sizeof(value)); - if(error) - { - if(errPtr) - { - NSString *errMsg = @"Unable to enable broadcast message sending"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; - } - return NO; - } - } - - // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. - // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. - - return YES; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Disconnect Implementation: -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)emptyQueues -{ - if (theCurrentSend) [self endCurrentSend]; - if (theCurrentReceive) [self endCurrentReceive]; - - [theSendQueue removeAllObjects]; - [theReceiveQueue removeAllObjects]; - - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueSend) object:nil]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueReceive) object:nil]; - - theFlags &= ~kDequeueSendScheduled; - theFlags &= ~kDequeueReceiveScheduled; -} - -- (void)closeSocket4 -{ - if (theSocket4 != NULL) - { - CFSocketInvalidate(theSocket4); - CFRelease(theSocket4); - theSocket4 = NULL; - } - if (theSource4 != NULL) - { - [self runLoopRemoveSource:theSource4]; - CFRelease(theSource4); - theSource4 = NULL; - } -} - -- (void)closeSocket6 -{ - if (theSocket6 != NULL) - { - CFSocketInvalidate(theSocket6); - CFRelease(theSocket6); - theSocket6 = NULL; - } - if (theSource6 != NULL) - { - [self runLoopRemoveSource:theSource6]; - CFRelease(theSource6); - theSource6 = NULL; - } -} - -- (void)close -{ - [self emptyQueues]; - [self closeSocket4]; - [self closeSocket6]; - - theRunLoop = NULL; - - // Delay notification to give user freedom to release without returning here and core-dumping. - if ([theDelegate respondsToSelector:@selector(onUdpSocketDidClose:)]) - { - [theDelegate performSelector:@selector(onUdpSocketDidClose:) - withObject:self - afterDelay:0 - inModes:theRunLoopModes]; - } - - theFlags |= kDidClose; -} - -- (void)closeAfterSending -{ - if(theFlags & kDidClose) return; - - theFlags |= (kForbidSendReceive | kCloseAfterSends); - [self maybeScheduleClose]; -} - -- (void)closeAfterReceiving -{ - if(theFlags & kDidClose) return; - - theFlags |= (kForbidSendReceive | kCloseAfterReceives); - [self maybeScheduleClose]; -} - -- (void)closeAfterSendingAndReceiving -{ - if(theFlags & kDidClose) return; - - theFlags |= (kForbidSendReceive | kCloseAfterSends | kCloseAfterReceives); - [self maybeScheduleClose]; -} - -- (void)maybeScheduleClose -{ - BOOL shouldDisconnect = NO; - - if(theFlags & kCloseAfterSends) - { - if(([theSendQueue count] == 0) && (theCurrentSend == nil)) - { - if(theFlags & kCloseAfterReceives) - { - if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - } - } - else if(theFlags & kCloseAfterReceives) - { - if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) - { - shouldDisconnect = YES; - } - } - - if(shouldDisconnect) - { - [self performSelector:@selector(close) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns a standard error object for the current errno value. - * Errno is used for low-level BSD socket errors. -**/ -- (NSError *)getErrnoError -{ - NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; -} - -/** - * Returns a standard error message for a CFSocket error. - * Unfortunately, CFSocket offers no feedback on its errors. -**/ -- (NSError *)getSocketError -{ - NSString *errMsg = @"General CFSocket error"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketCFSocketError userInfo:info]; -} - -- (NSError *)getIPv4UnavailableError -{ - NSString *errMsg = @"IPv4 is unavailable due to binding/connecting using IPv6 only"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv4Unavailable userInfo:info]; -} - -- (NSError *)getIPv6UnavailableError -{ - NSString *errMsg = @"IPv6 is unavailable due to binding/connecting using IPv4 only or is not supported on this platform"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv6Unavailable userInfo:info]; -} - -- (NSError *)getSendTimeoutError -{ - NSString *errMsg = @"Send operation timed out"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketSendTimeoutError userInfo:info]; -} -- (NSError *)getReceiveTimeoutError -{ - NSString *errMsg = @"Receive operation timed out"; - NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; - - return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketReceiveTimeoutError userInfo:info]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Diagnostics -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (NSString *)localHost -{ - if(cachedLocalHost) return cachedLocalHost; - - if(theSocket4) - return [self localHost:theSocket4]; - else - return [self localHost:theSocket6]; -} - -- (UInt16)localPort -{ - if(cachedLocalPort > 0) return cachedLocalPort; - - if(theSocket4) - return [self localPort:theSocket4]; - else - return [self localPort:theSocket6]; -} - -- (NSString *)connectedHost -{ - if(cachedConnectedHost) return cachedConnectedHost; - - if(theSocket4) - return [self connectedHost:theSocket4]; - else - return [self connectedHost:theSocket6]; -} - -- (UInt16)connectedPort -{ - if(cachedConnectedPort > 0) return cachedConnectedPort; - - if(theSocket4) - return [self connectedPort:theSocket4]; - else - return [self connectedPort:theSocket6]; -} - -- (NSString *)localHost:(CFSocketRef)theSocket -{ - if (theSocket == NULL) return nil; - - // Unfortunately we can't use CFSocketCopyAddress. - // The CFSocket library caches the address the first time you call CFSocketCopyAddress. - // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary, - // and will continue to return the old value of the socket address. - - NSString *result = nil; - - if (theSocket == theSocket4) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - result = [self addressHost4:&sockaddr4]; - } - else - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - result = [self addressHost6:&sockaddr6]; - } - - if (theFlags & kDidBind) - { - cachedLocalHost = [result copy]; - } - - return result; -} - -- (UInt16)localPort:(CFSocketRef)theSocket -{ - if (theSocket == NULL) return 0; - - // Unfortunately we can't use CFSocketCopyAddress. - // The CFSocket library caches the address the first time you call CFSocketCopyAddress. - // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary, - // and will continue to return the old value of the socket address. - - UInt16 result = 0; - - if (theSocket == theSocket4) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - result = ntohs(sockaddr4.sin_port); - } - else - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - result = ntohs(sockaddr6.sin6_port); - } - - if (theFlags & kDidBind) - { - cachedLocalPort = result; - } - - return result; -} - -- (NSString *)connectedHost:(CFSocketRef)theSocket -{ - if (theSocket == NULL) return nil; - - // Unfortunately we can't use CFSocketCopyPeerAddress. - // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress. - // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary, - // and will continue to return the old value of the socket peer address. - - NSString *result = nil; - - if (theSocket == theSocket4) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - result = [self addressHost4:&sockaddr4]; - } - else - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - result = [self addressHost6:&sockaddr6]; - } - - if (theFlags & kDidConnect) - { - cachedConnectedHost = [result copy]; - } - - return result; -} - -- (UInt16)connectedPort:(CFSocketRef)theSocket -{ - if(theSocket == NULL) return 0; - - // Unfortunately we can't use CFSocketCopyPeerAddress. - // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress. - // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary, - // and will continue to return the old value of the socket peer address. - - UInt16 result = 0; - - if(theSocket == theSocket4) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - result = ntohs(sockaddr4.sin_port); - } - else - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - result = ntohs(sockaddr6.sin6_port); - } - - if(theFlags & kDidConnect) - { - cachedConnectedPort = result; - } - - return result; -} - -- (BOOL)isConnected -{ - return (((theFlags & kDidConnect) != 0) && ((theFlags & kDidClose) == 0)); -} - -- (BOOL)isConnectedToHost:(NSString *)host port:(UInt16)port -{ - return [[self connectedHost] isEqualToString:host] && ([self connectedPort] == port); -} - -- (BOOL)isClosed -{ - return (theFlags & kDidClose) ? YES : NO; -} - -- (BOOL)isIPv4 -{ - return (theSocket4 != NULL); -} - -- (BOOL)isIPv6 -{ - return (theSocket6 != NULL); -} - -- (unsigned int)maximumTransmissionUnit -{ - CFSocketNativeHandle theNativeSocket; - if(theSocket4) - theNativeSocket = CFSocketGetNative(theSocket4); - else if(theSocket6) - theNativeSocket = CFSocketGetNative(theSocket6); - else - return 0; - - if(theNativeSocket == 0) - { - return 0; - } - - struct ifreq ifr; - bzero(&ifr, sizeof(ifr)); - - if(if_indextoname(theNativeSocket, ifr.ifr_name) == NULL) - { - return 0; - } - - if(ioctl(theNativeSocket, SIOCGIFMTU, &ifr) >= 0) - { - return ifr.ifr_mtu; - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Sending -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if([data length] == 0) return NO; - if(theFlags & kForbidSendReceive) return NO; - if(theFlags & kDidClose) return NO; - - // This method is only for connected sockets - if(![self isConnected]) return NO; - - AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:nil timeout:timeout tag:tag]; - - [theSendQueue addObject:packet]; - [self scheduleDequeueSend]; - - return YES; -} - -- (BOOL)sendData:(NSData *)data - toHost:(NSString *)host - port:(UInt16)port - withTimeout:(NSTimeInterval)timeout - tag:(long)tag -{ - if([data length] == 0) return NO; - if(theFlags & kForbidSendReceive) return NO; - if(theFlags & kDidClose) return NO; - - // This method is only for non-connected sockets - if([self isConnected]) return NO; - - NSData *address4 = nil, *address6 = nil; - [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6]; - - AsyncSendPacket *packet = nil; - - if(address4 && theSocket4) - packet = [[AsyncSendPacket alloc] initWithData:data address:address4 timeout:timeout tag:tag]; - else if(address6 && theSocket6) - packet = [[AsyncSendPacket alloc] initWithData:data address:address6 timeout:timeout tag:tag]; - else - return NO; - - [theSendQueue addObject:packet]; - [self scheduleDequeueSend]; - - return YES; -} - -- (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if([data length] == 0) return NO; - if(theFlags & kForbidSendReceive) return NO; - if(theFlags & kDidClose) return NO; - - // This method is only for non-connected sockets - if([self isConnected]) return NO; - - if([remoteAddr length] == sizeof(struct sockaddr_in) && !theSocket4) - return NO; - - if([remoteAddr length] == sizeof(struct sockaddr_in6) && !theSocket6) - return NO; - - AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:remoteAddr timeout:timeout tag:tag]; - - [theSendQueue addObject:packet]; - [self scheduleDequeueSend]; - - return YES; -} - -- (BOOL)canAcceptBytes:(CFSocketRef)sockRef -{ - if(sockRef == theSocket4) - { - if(theFlags & kSock4CanAcceptBytes) return YES; - } - else - { - if(theFlags & kSock6CanAcceptBytes) return YES; - } - - CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); - - if(theNativeSocket == 0) - { - NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); - return NO; - } - - fd_set fds; - FD_ZERO(&fds); - FD_SET(theNativeSocket, &fds); - - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 0; - - return select(FD_SETSIZE, NULL, &fds, NULL, &timeout) > 0; -} - -- (CFSocketRef)socketForPacket:(AsyncSendPacket *)packet -{ - if(!theSocket4) - return theSocket6; - if(!theSocket6) - return theSocket4; - - return ([packet->address length] == sizeof(struct sockaddr_in)) ? theSocket4 : theSocket6; -} - -/** - * Puts a maybeDequeueSend on the run loop. -**/ -- (void)scheduleDequeueSend -{ - if((theFlags & kDequeueSendScheduled) == 0) - { - theFlags |= kDequeueSendScheduled; - [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -/** - * This method starts a new send, if needed. - * It is called when a user requests a send. -**/ -- (void)maybeDequeueSend -{ - // Unset the flag indicating a call to this method is scheduled - theFlags &= ~kDequeueSendScheduled; - - if(theCurrentSend == nil) - { - if([theSendQueue count] > 0) - { - // Dequeue next send packet - theCurrentSend = [theSendQueue objectAtIndex:0]; - [theSendQueue removeObjectAtIndex:0]; - - // Start time-out timer. - if(theCurrentSend->timeout >= 0.0) - { - theSendTimer = [NSTimer timerWithTimeInterval:theCurrentSend->timeout - target:self - selector:@selector(doSendTimeout:) - userInfo:nil - repeats:NO]; - - [self runLoopAddTimer:theSendTimer]; - } - - // Immediately send, if possible. - [self doSend:[self socketForPacket:theCurrentSend]]; - } - else if(theFlags & kCloseAfterSends) - { - if(theFlags & kCloseAfterReceives) - { - if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) - { - [self close]; - } - } - else - { - [self close]; - } - } - } -} - -/** - * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. -**/ -- (void)doSend:(CFSocketRef)theSocket -{ - if(theCurrentSend != nil) - { - if(theSocket != [self socketForPacket:theCurrentSend]) - { - // Current send is for the other socket - return; - } - - if([self canAcceptBytes:theSocket]) - { - ssize_t result; - CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); - - const void *buf = [theCurrentSend->buffer bytes]; - NSUInteger bufSize = [theCurrentSend->buffer length]; - - if([self isConnected]) - { - result = send(theNativeSocket, buf, (size_t)bufSize, 0); - } - else - { - const void *dst = [theCurrentSend->address bytes]; - NSUInteger dstSize = [theCurrentSend->address length]; - - result = sendto(theNativeSocket, buf, (size_t)bufSize, 0, dst, (socklen_t)dstSize); - } - - if(theSocket == theSocket4) - theFlags &= ~kSock4CanAcceptBytes; - else - theFlags &= ~kSock6CanAcceptBytes; - - if(result < 0) - { - [self failCurrentSend:[self getErrnoError]]; - } - else - { - // If it wasn't bound before, it's bound now - theFlags |= kDidBind; - - [self completeCurrentSend]; - } - - [self scheduleDequeueSend]; - } - else - { - // Request notification when the socket is ready to send more data - CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); - } - } -} - -- (void)completeCurrentSend -{ - NSAssert (theCurrentSend, @"Trying to complete current send when there is no current send."); - - if ([theDelegate respondsToSelector:@selector(onUdpSocket:didSendDataWithTag:)]) - { - [theDelegate onUdpSocket:self didSendDataWithTag:theCurrentSend->tag]; - } - - if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. -} - -- (void)failCurrentSend:(NSError *)error -{ - NSAssert (theCurrentSend, @"Trying to fail current send when there is no current send."); - - if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotSendDataWithTag:dueToError:)]) - { - [theDelegate onUdpSocket:self didNotSendDataWithTag:theCurrentSend->tag dueToError:error]; - } - - if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. -} - -/** - * Ends the current send, and all associated variables such as the send timer. -**/ -- (void)endCurrentSend -{ - NSAssert (theCurrentSend, @"Trying to end current send when there is no current send."); - - [theSendTimer invalidate]; - theSendTimer = nil; - - theCurrentSend = nil; -} - -- (void)doSendTimeout:(NSTimer *)timer -{ - if (timer != theSendTimer) return; // Old timer. Ignore it. - if (theCurrentSend != nil) - { - [self failCurrentSend:[self getSendTimeoutError]]; - [self scheduleDequeueSend]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Receiving -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag -{ - if(theFlags & kForbidSendReceive) return; - if(theFlags & kDidClose) return; - - AsyncReceivePacket *packet = [[AsyncReceivePacket alloc] initWithTimeout:timeout tag:tag]; - - [theReceiveQueue addObject:packet]; - [self scheduleDequeueReceive]; -} - -- (BOOL)hasBytesAvailable:(CFSocketRef)sockRef -{ - if(sockRef == theSocket4) - { - if(theFlags & kSock4HasBytesAvailable) return YES; - } - else - { - if(theFlags & kSock6HasBytesAvailable) return YES; - } - - CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); - - if(theNativeSocket == 0) - { - NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); - return NO; - } - - fd_set fds; - FD_ZERO(&fds); - FD_SET(theNativeSocket, &fds); - - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 0; - - return select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0; -} - -/** - * Puts a maybeDequeueReceive on the run loop. -**/ -- (void)scheduleDequeueReceive -{ - if((theFlags & kDequeueReceiveScheduled) == 0) - { - theFlags |= kDequeueReceiveScheduled; - [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes]; - } -} - -/** - * Starts a new receive operation if needed -**/ -- (void)maybeDequeueReceive -{ - // Unset the flag indicating a call to this method is scheduled - theFlags &= ~kDequeueReceiveScheduled; - - if (theCurrentReceive == nil) - { - if ([theReceiveQueue count] > 0) - { - // Dequeue next receive packet - theCurrentReceive = [theReceiveQueue objectAtIndex:0]; - [theReceiveQueue removeObjectAtIndex:0]; - - // Start time-out timer. - if (theCurrentReceive->timeout >= 0.0) - { - theReceiveTimer = [NSTimer timerWithTimeInterval:theCurrentReceive->timeout - target:self - selector:@selector(doReceiveTimeout:) - userInfo:nil - repeats:NO]; - - [self runLoopAddTimer:theReceiveTimer]; - } - - // Immediately receive, if possible - // We always check both sockets so we don't ever starve one of them. - // We also check them in alternating orders to prevent starvation if both of them - // have a continuous flow of incoming data. - if(theFlags & kFlipFlop) - { - [self doReceive4]; - [self doReceive6]; - } - else - { - [self doReceive6]; - [self doReceive4]; - } - - theFlags ^= kFlipFlop; - } - else if(theFlags & kCloseAfterReceives) - { - if(theFlags & kCloseAfterSends) - { - if(([theSendQueue count] == 0) && (theCurrentSend == nil)) - { - [self close]; - } - } - else - { - [self close]; - } - } - } -} - -- (void)doReceive4 -{ - if(theSocket4) [self doReceive:theSocket4]; -} - -- (void)doReceive6 -{ - if(theSocket6) [self doReceive:theSocket6]; -} - -- (void)doReceive:(CFSocketRef)theSocket -{ - if (theCurrentReceive != nil) - { - BOOL appIgnoredReceivedData; - BOOL userIgnoredReceivedData; - - do - { - // Set or reset ignored variables. - // If the app or user ignores the received data, we'll continue this do-while loop. - appIgnoredReceivedData = NO; - userIgnoredReceivedData = NO; - - if([self hasBytesAvailable:theSocket]) - { - ssize_t result; - CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); - - // Allocate buffer for recvfrom operation. - // If the operation is successful, we'll realloc the buffer to the appropriate size, - // and create an NSData wrapper around it without needing to copy any bytes around. - void *buf = malloc(maxReceiveBufferSize); - size_t bufSize = maxReceiveBufferSize; - - if(theSocket == theSocket4) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); - - if(result >= 0) - { - NSString *host = [self addressHost4:&sockaddr4]; - UInt16 port = ntohs(sockaddr4.sin_port); - - if([self isConnected] && ![self isConnectedToHost:host port:port]) - { - // The user connected to an address, and the received data doesn't match the address. - // This may happen if the data is received by the kernel prior to the connect call. - appIgnoredReceivedData = YES; - } - else - { - if(result != bufSize) - { - buf = realloc(buf, result); - } - theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf - length:result - freeWhenDone:YES]; - theCurrentReceive->host = host; - theCurrentReceive->port = port; - } - } - - theFlags &= ~kSock4HasBytesAvailable; - } - else - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); - - if(result >= 0) - { - NSString *host = [self addressHost6:&sockaddr6]; - UInt16 port = ntohs(sockaddr6.sin6_port); - - if([self isConnected] && ![self isConnectedToHost:host port:port]) - { - // The user connected to an address, and the received data doesn't match the address. - // This may happen if the data is received by the kernel prior to the connect call. - appIgnoredReceivedData = YES; - } - else - { - if(result != bufSize) - { - buf = realloc(buf, result); - } - theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf - length:result - freeWhenDone:YES]; - theCurrentReceive->host = host; - theCurrentReceive->port = port; - } - } - - theFlags &= ~kSock6HasBytesAvailable; - } - - // Check to see if we need to free our alloc'd buffer - // If the buffer is non-nil, this means it has taken ownership of the buffer - if(theCurrentReceive->buffer == nil) - { - free(buf); - } - - if(result < 0) - { - [self failCurrentReceive:[self getErrnoError]]; - [self scheduleDequeueReceive]; - } - else if(!appIgnoredReceivedData) - { - BOOL finished = [self maybeCompleteCurrentReceive]; - - if(finished) - { - [self scheduleDequeueReceive]; - } - else - { - theCurrentReceive->buffer = nil; - theCurrentReceive->host = nil; - - userIgnoredReceivedData = YES; - } - } - } - else - { - // Request notification when the socket is ready to receive more data - CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); - } - - } while(appIgnoredReceivedData || userIgnoredReceivedData); - } -} - -- (BOOL)maybeCompleteCurrentReceive -{ - NSAssert (theCurrentReceive, @"Trying to complete current receive when there is no current receive."); - - BOOL finished = YES; - - if ([theDelegate respondsToSelector:@selector(onUdpSocket:didReceiveData:withTag:fromHost:port:)]) - { - finished = [theDelegate onUdpSocket:self - didReceiveData:theCurrentReceive->buffer - withTag:theCurrentReceive->tag - fromHost:theCurrentReceive->host - port:theCurrentReceive->port]; - } - - if (finished) - { - if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. - } - return finished; -} - -- (void)failCurrentReceive:(NSError *)error -{ - NSAssert (theCurrentReceive, @"Trying to fail current receive when there is no current receive."); - - if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotReceiveDataWithTag:dueToError:)]) - { - [theDelegate onUdpSocket:self didNotReceiveDataWithTag:theCurrentReceive->tag dueToError:error]; - } - - if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. -} - -- (void)endCurrentReceive -{ - NSAssert (theCurrentReceive, @"Trying to end current receive when there is no current receive."); - - [theReceiveTimer invalidate]; - theReceiveTimer = nil; - - theCurrentReceive = nil; -} - -- (void)doReceiveTimeout:(NSTimer *)timer -{ - if (timer != theReceiveTimer) return; // Old timer. Ignore it. - if (theCurrentReceive != nil) - { - [self failCurrentReceive:[self getReceiveTimeoutError]]; - [self scheduleDequeueReceive]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark CF Callbacks -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)doCFSocketCallback:(CFSocketCallBackType)type - forSocket:(CFSocketRef)sock - withAddress:(NSData *)address - withData:(const void *)pData -{ - NSParameterAssert((sock == theSocket4) || (sock == theSocket6)); - - switch (type) - { - case kCFSocketReadCallBack: - if(sock == theSocket4) - theFlags |= kSock4HasBytesAvailable; - else - theFlags |= kSock6HasBytesAvailable; - [self doReceive:sock]; - break; - case kCFSocketWriteCallBack: - if(sock == theSocket4) - theFlags |= kSock4CanAcceptBytes; - else - theFlags |= kSock6CanAcceptBytes; - [self doSend:sock]; - break; - default: - NSLog (@"AsyncUdpSocket %p received unexpected CFSocketCallBackType %lu.", self, (unsigned long)type); - break; - } -} - -/** - * This is the callback we setup for CFSocket. - * This method does nothing but forward the call to it's Objective-C counterpart -**/ -static void MyCFSocketCallback(CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) -{ - @autoreleasepool { - - AsyncUdpSocket *theSocket = (__bridge AsyncUdpSocket *)pInfo; - [theSocket doCFSocketCallback:type forSocket:sref withAddress:(__bridge NSData *)address withData:pData]; - - } -} - -@end diff --git a/Pods/Headers/CocoaAsyncSocket/AsyncSocket.h b/Pods/Headers/CocoaAsyncSocket/AsyncSocket.h deleted file mode 120000 index afce6cb..0000000 --- a/Pods/Headers/CocoaAsyncSocket/AsyncSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/RunLoop/AsyncSocket.h \ No newline at end of file diff --git a/Pods/Headers/CocoaAsyncSocket/AsyncUdpSocket.h b/Pods/Headers/CocoaAsyncSocket/AsyncUdpSocket.h deleted file mode 120000 index 0d51519..0000000 --- a/Pods/Headers/CocoaAsyncSocket/AsyncUdpSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h \ No newline at end of file diff --git a/Pods/Headers/CocoaAsyncSocket/GCDAsyncSocket.h b/Pods/Headers/CocoaAsyncSocket/GCDAsyncSocket.h deleted file mode 120000 index 696efcd..0000000 --- a/Pods/Headers/CocoaAsyncSocket/GCDAsyncSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/GCD/GCDAsyncSocket.h \ No newline at end of file diff --git a/Pods/Headers/CocoaAsyncSocket/GCDAsyncUdpSocket.h b/Pods/Headers/CocoaAsyncSocket/GCDAsyncUdpSocket.h deleted file mode 120000 index eef8ea2..0000000 --- a/Pods/Headers/CocoaAsyncSocket/GCDAsyncUdpSocket.h +++ /dev/null @@ -1 +0,0 @@ -../../CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h \ No newline at end of file diff --git a/Pods/Pods-Acknowledgements.markdown b/Pods/Pods-Acknowledgements.markdown deleted file mode 100644 index 255149a..0000000 --- a/Pods/Pods-Acknowledgements.markdown +++ /dev/null @@ -1,3 +0,0 @@ -# Acknowledgements -This application makes use of the following third party libraries: -Generated by CocoaPods - http://cocoapods.org diff --git a/Pods/Pods-Acknowledgements.plist b/Pods/Pods-Acknowledgements.plist deleted file mode 100644 index e4edebe..0000000 --- a/Pods/Pods-Acknowledgements.plist +++ /dev/null @@ -1,29 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - http://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Pods/Pods-prefix.pch b/Pods/Pods-prefix.pch deleted file mode 100644 index 00e8847..0000000 --- a/Pods/Pods-prefix.pch +++ /dev/null @@ -1,3 +0,0 @@ -#ifdef __OBJC__ -#import -#endif diff --git a/Pods/Pods-resources.sh b/Pods/Pods-resources.sh deleted file mode 100755 index f717249..0000000 --- a/Pods/Pods-resources.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -install_resource() -{ - case $1 in - *.storyboard) - echo "ibtool --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" - ibtool --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" - ;; - *.xib) - echo "ibtool --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" - ibtool --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" - ;; - *.framework) - echo "rsync -rp ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - rsync -rp "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - ;; - *) - echo "cp -R ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - cp -R "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" - ;; - esac -} diff --git a/Pods/Pods.xcconfig b/Pods/Pods.xcconfig deleted file mode 100644 index c2e3d9e..0000000 --- a/Pods/Pods.xcconfig +++ /dev/null @@ -1,7 +0,0 @@ -PODS_ROOT = ${SRCROOT}/Pods -PODS_PUBLIC_HEADERS_SEARCH_PATHS = "${PODS_ROOT}/Headers" "${PODS_ROOT}/Headers/CocoaAsyncSocket" -ALWAYS_SEARCH_USER_PATHS = YES -OTHER_LDFLAGS = -ObjC -framework CoreServices -framework Security -PODS_HEADERS_SEARCH_PATHS = ${PODS_PUBLIC_HEADERS_SEARCH_PATHS} -HEADER_SEARCH_PATHS = ${PODS_HEADERS_SEARCH_PATHS} -PODS_BUILD_HEADERS_SEARCH_PATHS = "${PODS_ROOT}/BuildHeaders" "${PODS_ROOT}/BuildHeaders/CocoaAsyncSocket" \ No newline at end of file diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj deleted file mode 100644 index cf417d7..0000000 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ /dev/null @@ -1,711 +0,0 @@ - - - - - archiveVersion - 1 - classes - - objectVersion - 46 - objects - - 0F070F3C76084E1289B15A3A - - children - - D929508EE8444CD5A32B4762 - - isa - PBXGroup - name - Products - sourceTree - <group> - - 12434734DFB8413CA63E3C71 - - buildConfigurationList - 6D9B85590359405F9FF5461E - buildPhases - - 92E745A3631442C186EE5046 - 4061328774E24D57AA5C4B9A - 638237BAFDB146DAB8A71866 - - buildRules - - dependencies - - isa - PBXNativeTarget - name - Pods - productName - Pods - productReference - D929508EE8444CD5A32B4762 - productType - com.apple.product-type.library.static - - 195BE243E38647AEB6467359 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - name - AsyncSocket.m - path - CocoaAsyncSocket/RunLoop/AsyncSocket.m - sourceTree - SOURCE_ROOT - - 1A498AECD180440FBC06C443 - - fileRef - 5C886D48A84F49A98FBCCA53 - isa - PBXBuildFile - settings - - - 1B9F0233A9264D78B1F2335A - - buildSettings - - isa - XCBuildConfiguration - name - Release - - 22EDA60D262E4BB8821001A3 - - fileRef - 195BE243E38647AEB6467359 - isa - PBXBuildFile - settings - - COMPILER_FLAGS - -fobjc-arc - - - 295246978B6A426E8C1DB740 - - children - - C3255357092748AABBBE47B2 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - 2DABFED9A6B344CA927F0C4B - - includeInIndex - 1 - isa - PBXFileReference - name - Pods-resources.sh - path - Pods-resources.sh - sourceTree - SOURCE_ROOT - - 3469D94ED2D74B7D80FBFF46 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - name - GCDAsyncSocket.h - path - CocoaAsyncSocket/GCD/GCDAsyncSocket.h - sourceTree - SOURCE_ROOT - - 350305707AE0451499E35109 - - children - - 0F070F3C76084E1289B15A3A - B705B5F2C2174AC89CDD05EE - 295246978B6A426E8C1DB740 - B9DFA40F71E14967936079D4 - - isa - PBXGroup - sourceTree - <group> - - 4061328774E24D57AA5C4B9A - - buildActionMask - 2147483647 - files - - A89C6AE966BC40429B8F491E - - isa - PBXFrameworksBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 428B4AFA96A64D1DB0DC1576 - - fileRef - 3469D94ED2D74B7D80FBFF46 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 47852BB774E1405097B3BC12 - - fileRef - C549C7CA6F6F49D1BB9BF677 - isa - PBXBuildFile - settings - - COMPILER_FLAGS - -fobjc-arc - - - 4822582EB6D34517AC45DBB8 - - baseConfigurationReference - 519A80BB09964BBB86EF9733 - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - ARCHS - $(ARCHS_STANDARD_64_BIT) - COMBINE_HIDPI_IMAGES - YES - COPY_PHASE_STRIP - YES - DEBUG_INFORMATION_FORMAT - dwarf-with-dsym - DSTROOT - /tmp/xcodeproj.dst - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_ENABLE_OBJC_EXCEPTIONS - YES - GCC_PRECOMPILE_PREFIX_HEADER - YES - GCC_PREFIX_HEADER - Pods-prefix.pch - GCC_VERSION - com.apple.compilers.llvm.clang.1_0 - GCC_WARN_INHIBIT_ALL_WARNINGS - NO - INSTALL_PATH - $(BUILT_PRODUCTS_DIR) - MACOSX_DEPLOYMENT_TARGET - 10.7 - OTHER_LDFLAGS - - PODS_HEADERS_SEARCH_PATHS - ${PODS_BUILD_HEADERS_SEARCH_PATHS} - PODS_ROOT - ${SRCROOT} - PRODUCT_NAME - $(TARGET_NAME) - SDKROOT - macosx - SKIP_INSTALL - YES - - isa - XCBuildConfiguration - name - Release - - 4D07BF80C1A44B9CA13423BE - - buildConfigurations - - 1B9F0233A9264D78B1F2335A - A6E81A681EAE49AC91F41F44 - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 519A80BB09964BBB86EF9733 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - text.xcconfig - name - Pods.xcconfig - path - Pods.xcconfig - sourceTree - SOURCE_ROOT - - 5544DD8F7893439299B2FF58 - - includeInIndex - 1 - isa - PBXFileReference - name - Pods-prefix.pch - path - Pods-prefix.pch - sourceTree - SOURCE_ROOT - - 5C886D48A84F49A98FBCCA53 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - name - PodsDummy_Pods.m - path - PodsDummy_Pods.m - sourceTree - SOURCE_ROOT - - 5E2FB0A37B1F40E7A1C2A77C - - fileRef - 713E4C7A0B504A03929A174D - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 612868D5DE0C4A13A26C6BAC - - attributes - - LastUpgradeCheck - 0450 - - buildConfigurationList - 4D07BF80C1A44B9CA13423BE - compatibilityVersion - Xcode 3.2 - developmentRegion - English - hasScannedForEncodings - 0 - isa - PBXProject - knownRegions - - en - - mainGroup - 350305707AE0451499E35109 - productRefGroup - 0F070F3C76084E1289B15A3A - projectReferences - - targets - - 12434734DFB8413CA63E3C71 - - - 638237BAFDB146DAB8A71866 - - buildActionMask - 2147483647 - files - - 428B4AFA96A64D1DB0DC1576 - 5E2FB0A37B1F40E7A1C2A77C - 8B2AFC64196E41ABB37CA014 - 8C76AD2AF500469C868A459E - - isa - PBXHeadersBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - 6D9B85590359405F9FF5461E - - buildConfigurations - - 4822582EB6D34517AC45DBB8 - A4E54D77920B42AA9131965D - - defaultConfigurationIsVisible - 0 - defaultConfigurationName - Release - isa - XCConfigurationList - - 713E4C7A0B504A03929A174D - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - name - GCDAsyncUdpSocket.h - path - CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h - sourceTree - SOURCE_ROOT - - 7571ADEDC42147409030EDC3 - - fileRef - C132684BBEA74F32858B93AE - isa - PBXBuildFile - settings - - COMPILER_FLAGS - -fobjc-arc - - - 8B2AFC64196E41ABB37CA014 - - fileRef - E364DC5C9D454ACC9AD04998 - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 8C76AD2AF500469C868A459E - - fileRef - A9F8994165234655939E77CE - isa - PBXBuildFile - settings - - ATTRIBUTES - - Public - - - - 92E745A3631442C186EE5046 - - buildActionMask - 2147483647 - files - - 47852BB774E1405097B3BC12 - 7571ADEDC42147409030EDC3 - 22EDA60D262E4BB8821001A3 - BED5D65C145D41E3A920C693 - 1A498AECD180440FBC06C443 - - isa - PBXSourcesBuildPhase - runOnlyForDeploymentPostprocessing - 0 - - A4E54D77920B42AA9131965D - - baseConfigurationReference - 519A80BB09964BBB86EF9733 - buildSettings - - ALWAYS_SEARCH_USER_PATHS - NO - ARCHS - $(ARCHS_STANDARD_64_BIT) - COMBINE_HIDPI_IMAGES - YES - COPY_PHASE_STRIP - NO - DSTROOT - /tmp/xcodeproj.dst - GCC_C_LANGUAGE_STANDARD - gnu99 - GCC_DYNAMIC_NO_PIC - NO - GCC_ENABLE_OBJC_EXCEPTIONS - YES - GCC_OPTIMIZATION_LEVEL - 0 - GCC_PRECOMPILE_PREFIX_HEADER - YES - GCC_PREFIX_HEADER - Pods-prefix.pch - GCC_PREPROCESSOR_DEFINITIONS - - DEBUG=1 - $(inherited) - - GCC_SYMBOLS_PRIVATE_EXTERN - NO - GCC_VERSION - com.apple.compilers.llvm.clang.1_0 - GCC_WARN_INHIBIT_ALL_WARNINGS - NO - INSTALL_PATH - $(BUILT_PRODUCTS_DIR) - MACOSX_DEPLOYMENT_TARGET - 10.7 - ONLY_ACTIVE_ARCH - YES - OTHER_LDFLAGS - - PODS_HEADERS_SEARCH_PATHS - ${PODS_BUILD_HEADERS_SEARCH_PATHS} - PODS_ROOT - ${SRCROOT} - PRODUCT_NAME - $(TARGET_NAME) - SDKROOT - macosx - SKIP_INSTALL - YES - - isa - XCBuildConfiguration - name - Debug - - A6E81A681EAE49AC91F41F44 - - buildSettings - - isa - XCBuildConfiguration - name - Debug - - A89C6AE966BC40429B8F491E - - fileRef - D8595680AFFC4E539101240F - isa - PBXBuildFile - settings - - - A9F8994165234655939E77CE - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - name - AsyncUdpSocket.h - path - CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h - sourceTree - SOURCE_ROOT - - B705B5F2C2174AC89CDD05EE - - children - - D8595680AFFC4E539101240F - - isa - PBXGroup - name - Frameworks - sourceTree - <group> - - B9DFA40F71E14967936079D4 - - children - - FDA0DC13F1EC4F6D90BDA698 - 5C886D48A84F49A98FBCCA53 - - isa - PBXGroup - name - Targets Support Files - sourceTree - <group> - - BED5D65C145D41E3A920C693 - - fileRef - D1B6A93A70554D81A74F7AC5 - isa - PBXBuildFile - settings - - COMPILER_FLAGS - -fobjc-arc - - - C132684BBEA74F32858B93AE - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - name - GCDAsyncUdpSocket.m - path - CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m - sourceTree - SOURCE_ROOT - - C3255357092748AABBBE47B2 - - children - - 3469D94ED2D74B7D80FBFF46 - 713E4C7A0B504A03929A174D - C549C7CA6F6F49D1BB9BF677 - C132684BBEA74F32858B93AE - E364DC5C9D454ACC9AD04998 - A9F8994165234655939E77CE - 195BE243E38647AEB6467359 - D1B6A93A70554D81A74F7AC5 - - isa - PBXGroup - name - CocoaAsyncSocket - sourceTree - <group> - - C549C7CA6F6F49D1BB9BF677 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - name - GCDAsyncSocket.m - path - CocoaAsyncSocket/GCD/GCDAsyncSocket.m - sourceTree - SOURCE_ROOT - - D1B6A93A70554D81A74F7AC5 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.objc - name - AsyncUdpSocket.m - path - CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m - sourceTree - SOURCE_ROOT - - D8595680AFFC4E539101240F - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - wrapper.framework - name - Cocoa.framework - path - System/Library/Frameworks/Cocoa.framework - sourceTree - SDKROOT - - D929508EE8444CD5A32B4762 - - explicitFileType - archive.ar - includeInIndex - 0 - isa - PBXFileReference - name - libPods.a - path - libPods.a - sourceTree - BUILT_PRODUCTS_DIR - - E364DC5C9D454ACC9AD04998 - - includeInIndex - 1 - isa - PBXFileReference - lastKnownFileType - sourcecode.c.h - name - AsyncSocket.h - path - CocoaAsyncSocket/RunLoop/AsyncSocket.h - sourceTree - SOURCE_ROOT - - FDA0DC13F1EC4F6D90BDA698 - - children - - 2DABFED9A6B344CA927F0C4B - 5544DD8F7893439299B2FF58 - 519A80BB09964BBB86EF9733 - - isa - PBXGroup - name - Pods - sourceTree - <group> - - - rootObject - 612868D5DE0C4A13A26C6BAC - - diff --git a/Pods/PodsDummy_Pods.m b/Pods/PodsDummy_Pods.m deleted file mode 100644 index 99f3235..0000000 --- a/Pods/PodsDummy_Pods.m +++ /dev/null @@ -1,4 +0,0 @@ -@interface PodsDummy_Pods : NSObject -@end -@implementation PodsDummy_Pods -@end diff --git a/QuickHue.xcodeproj/project.pbxproj b/QuickHue.xcodeproj/project.pbxproj index 43b15fe..ba2d70e 100644 --- a/QuickHue.xcodeproj/project.pbxproj +++ b/QuickHue.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 30C0CF4C169695970052AB04 /* bulb-black.png in Resources */ = {isa = PBXBuildFile; fileRef = 30C0CF48169695970052AB04 /* bulb-black.png */; }; + 30C0CF4D169695970052AB04 /* bulb-black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30C0CF49169695970052AB04 /* bulb-black@2x.png */; }; + 30C0CF4E169695970052AB04 /* bulb-white.png in Resources */ = {isa = PBXBuildFile; fileRef = 30C0CF4A169695970052AB04 /* bulb-white.png */; }; + 30C0CF4F169695970052AB04 /* bulb-white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30C0CF4B169695970052AB04 /* bulb-white@2x.png */; }; 373B600F1682D7D700E07569 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 373B600E1682D7D700E07569 /* Cocoa.framework */; }; 373B60191682D7D700E07569 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 373B60171682D7D700E07569 /* InfoPlist.strings */; }; 373B601B1682D7D700E07569 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 373B601A1682D7D700E07569 /* main.m */; }; @@ -33,6 +37,10 @@ /* Begin PBXFileReference section */ 2A22B7CA18F1463E9D41C163 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 30C0CF48169695970052AB04 /* bulb-black.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "bulb-black.png"; path = "Images/bulb-black.png"; sourceTree = ""; }; + 30C0CF49169695970052AB04 /* bulb-black@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "bulb-black@2x.png"; path = "Images/bulb-black@2x.png"; sourceTree = ""; }; + 30C0CF4A169695970052AB04 /* bulb-white.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "bulb-white.png"; path = "Images/bulb-white.png"; sourceTree = ""; }; + 30C0CF4B169695970052AB04 /* bulb-white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "bulb-white@2x.png"; path = "Images/bulb-white@2x.png"; sourceTree = ""; }; 373B600A1682D7D700E07569 /* QuickHue.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickHue.app; sourceTree = BUILT_PRODUCTS_DIR; }; 373B600E1682D7D700E07569 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 373B60111682D7D700E07569 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -185,6 +193,10 @@ 373B60BA168576D000E07569 /* Images */ = { isa = PBXGroup; children = ( + 30C0CF48169695970052AB04 /* bulb-black.png */, + 30C0CF49169695970052AB04 /* bulb-black@2x.png */, + 30C0CF4A169695970052AB04 /* bulb-white.png */, + 30C0CF4B169695970052AB04 /* bulb-white@2x.png */, 37F4DFF91686E65900D508D5 /* checkmark.png */, 37F4DFFA1686E65900D508D5 /* checkmark@2x.png */, 37F4DFF71686935300D508D5 /* appicon.icns */, @@ -256,6 +268,10 @@ 37F4DFF81686935300D508D5 /* appicon.icns in Resources */, 37F4DFFB1686E65900D508D5 /* checkmark.png in Resources */, 37F4DFFC1686E65900D508D5 /* checkmark@2x.png in Resources */, + 30C0CF4C169695970052AB04 /* bulb-black.png in Resources */, + 30C0CF4D169695970052AB04 /* bulb-black@2x.png in Resources */, + 30C0CF4E169695970052AB04 /* bulb-white.png in Resources */, + 30C0CF4F169695970052AB04 /* bulb-white@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/QuickHue/DPQuickHueAppDelegate.m b/QuickHue/DPQuickHueAppDelegate.m index 6d310ad..690f922 100644 --- a/QuickHue/DPQuickHueAppDelegate.m +++ b/QuickHue/DPQuickHueAppDelegate.m @@ -15,7 +15,7 @@ #import "DPQuickHuePrefsViewController.h" @interface DPQuickHueAppDelegate () -@property (nonatomic, strong) NSStatusItem *statusBar; +@property (nonatomic, strong) NSStatusItem *statusItem; @property (nonatomic, strong) NSMenu *statusBarMenu; @property (nonatomic, strong) DPQuickHuePrefsViewController *pvc; @property (nonatomic, strong) DPHueDiscover *dhd; @@ -23,19 +23,31 @@ @interface DPQuickHueAppDelegate () extern NSString * const QuickHueAPIUsernamePrefKey; extern NSString * const QuickHueHostPrefKey; +extern NSString * const QuickHueUseBlackAndWhiteMenuBarIconsKey; @implementation DPQuickHueAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - self.statusBar = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; - self.statusBar.image = [NSImage imageNamed:@"bulb"]; - self.statusBar.highlightMode = YES; + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + + self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + + if([prefs boolForKey:QuickHueUseBlackAndWhiteMenuBarIconsKey]) { + self.statusItem.image = [NSImage imageNamed:@"bulb-black"]; + self.statusItem.alternateImage = [NSImage imageNamed:@"bulb-white"]; + } else { + self.statusItem.image = [NSImage imageNamed:@"bulb"]; + self.statusItem.alternateImage = [NSImage imageNamed:@"bulb"]; + } + + self.statusItem.highlightMode = YES; + [self buildMenu]; self.pvc = [[DPQuickHuePrefsViewController alloc] init]; + self.pvc.statusItem = self.statusItem; self.pvc.delegate = self; - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; if ( (![prefs objectForKey:QuickHueAPIUsernamePrefKey]) || (![prefs objectForKey:QuickHueHostPrefKey])) { self.pvc.firstRun = YES; @@ -68,7 +80,7 @@ - (void)buildMenu { [self.statusBarMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Create Preset" action:@selector(createPreset) keyEquivalent:@""]]; [self.statusBarMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(preferences) keyEquivalent:@""]]; [self.statusBarMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@""]]; - self.statusBar.menu = self.statusBarMenu; + self.statusItem.menu = self.statusBarMenu; } - (void)applyPreset:(id)sender { diff --git a/QuickHue/DPQuickHuePrefsViewController.h b/QuickHue/DPQuickHuePrefsViewController.h index 2b66797..6d61ee5 100644 --- a/QuickHue/DPQuickHuePrefsViewController.h +++ b/QuickHue/DPQuickHuePrefsViewController.h @@ -13,12 +13,15 @@ @property (nonatomic) BOOL firstRun; +@property (nonatomic, strong) NSStatusItem *statusItem; //for modifying black and white vs. color state + // Properties of main prefs window @property (nonatomic, strong) id delegate; @property (weak) IBOutlet NSWindow *window; @property (weak) IBOutlet NSPanel *discoverySheet; @property (weak) IBOutlet NSTextField *hueBridgeHostLabel; @property (weak) IBOutlet NSButton *launchAtLoginCheckbox; +@property (weak) IBOutlet NSButton *useBlackAndWhiteMenuBarIconsCheckbox; @property (weak) IBOutlet NSTableView *presetsTableView; @property (weak) IBOutlet NSButton *removePresetButton; @property (weak) IBOutlet NSTextField *twitterLabel; @@ -35,12 +38,12 @@ - (IBAction)removePreset:(id)sender; - (IBAction)tableViewSelected:(id)sender; - (IBAction)startAtLoginClicked:(id)sender; +- (IBAction)useBlackAndWhiteMenuBarIconsClicked:(id)sender; - (IBAction)startDiscovery:(id)sender; - (void)updateLaunchAtLoginCheckbox; - (IBAction)triggerTouchlink:(id)sender; - // Properties of sheet @property (weak) IBOutlet NSProgressIndicator *discoveryProgressIndicator; @property (weak) IBOutlet NSTextField *discoveryStatusLabel; diff --git a/QuickHue/DPQuickHuePrefsViewController.m b/QuickHue/DPQuickHuePrefsViewController.m index a8752e8..116eb69 100644 --- a/QuickHue/DPQuickHuePrefsViewController.m +++ b/QuickHue/DPQuickHuePrefsViewController.m @@ -16,6 +16,7 @@ NSString *const QuickHueAPIUsernamePrefKey = @"QuickHueAPIUsernamePrefKey"; NSString *const QuickHueHostPrefKey = @"QuickHueHostPrefKey"; +NSString *const QuickHueUseBlackAndWhiteMenuBarIconsKey = @"QuickHueUseBlackAndWhiteMenuBarIcons"; @interface DPQuickHuePrefsViewController () @property (nonatomic, strong) DPHueDiscover *dhd; @@ -74,6 +75,8 @@ - (void)loadView { [self.githubLabel sizeToFit]; self.versionLabel.stringValue = [NSString stringWithFormat:@"QuickHue v%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]; + + self.useBlackAndWhiteMenuBarIconsCheckbox.state = [[NSUserDefaults standardUserDefaults] boolForKey:QuickHueUseBlackAndWhiteMenuBarIconsKey]; } - (void)addLoginItem { @@ -260,6 +263,22 @@ - (IBAction)startAtLoginClicked:(id)sender { [self deleteLoginItem]; } +- (IBAction)useBlackAndWhiteMenuBarIconsClicked:(id)sender { + if(self.useBlackAndWhiteMenuBarIconsCheckbox.state) { + self.statusItem.image = [NSImage imageNamed:@"bulb-black"]; + self.statusItem.alternateImage = [NSImage imageNamed:@"bulb-white"]; + } else { + self.statusItem.image = [NSImage imageNamed:@"bulb"]; + self.statusItem.alternateImage = [NSImage imageNamed:@"bulb"]; + } + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + [defaults setBool:self.useBlackAndWhiteMenuBarIconsCheckbox.state forKey:QuickHueUseBlackAndWhiteMenuBarIconsKey]; + + [defaults synchronize]; +} + - (void)autosetRemovePresetButtonState { if (self.presetsTableView.numberOfSelectedRows != 0) [self.removePresetButton setEnabled:YES]; diff --git a/QuickHue/DPQuickHuePrefsViewController.xib b/QuickHue/DPQuickHuePrefsViewController.xib index 253622c..5d9d52d 100644 --- a/QuickHue/DPQuickHuePrefsViewController.xib +++ b/QuickHue/DPQuickHuePrefsViewController.xib @@ -2,7 +2,7 @@ 1070 - 12C3006 + 12C60 2844 1187.34 625.00 @@ -51,7 +51,7 @@ 7 2 - {{163, 199}, {408, 249}} + {{163, 199}, {495, 305}} 1685586944 QuickHue Preferences NSWindow @@ -64,7 +64,7 @@ 268 - {{204, 194}, {78, 17}} + {{249, 250}, {78, 17}} @@ -105,7 +105,7 @@ 268 - {{284, 194}, {127, 17}} + {{329, 250}, {127, 17}} @@ -127,7 +127,7 @@ 268 - {{207, 167}, {166, 19}} + {{267, 223}, {166, 19}} @@ -156,10 +156,10 @@ 268 - {{205, 144}, {107, 18}} + {{205, 118}, {107, 18}} - + _NS:9 YES @@ -171,11 +171,11 @@ 1211912448 2 - + NSImage NSSwitch - + NSSwitch @@ -185,6 +185,33 @@ NO + + + 268 + {{205, 98}, {250, 18}} + + + + _NS:9 + YES + + 67108864 + 301989888 + Use black and white menu bar icons + + _NS:9 + + 1211912448 + 2 + + + + + 200 + 25 + + NO + 268 @@ -280,7 +307,7 @@ 4352 - {168, 168} + {168, 225} @@ -367,7 +394,7 @@ 1 - {{1, 1}, {168, 168}} + {{1, 1}, {168, 225}} @@ -404,7 +431,7 @@ 0.99629629629629635 - {{20, 41}, {170, 170}} + {{20, 40}, {170, 227}} @@ -421,7 +448,7 @@ 268 - {{80, 219}, {50, 17}} + {{80, 275}, {50, 17}} @@ -442,10 +469,10 @@ 12 - {{196, 12}, {5, 225}} + {{196, 12}, {5, 281}} - + _NS:9 {0, 0} @@ -472,10 +499,10 @@ 268 - {{252, 220}, {77, 17}} + {{307, 153}, {77, 17}} - + _NS:1535 YES @@ -493,10 +520,10 @@ 268 - {{249, 45}, {96, 17}} + {{298, 46}, {96, 17}} - + _NS:1535 YES @@ -527,7 +554,7 @@ 268 - {{249, 32}, {17, 17}} + {{298, 33}, {17, 17}} @@ -548,10 +575,10 @@ 12 - {{265, 32}, {76, 17}} + {{314, 33}, {76, 17}} - + _NS:1535 YES @@ -575,7 +602,7 @@ 268 - {{303, 19}, {228, 17}} + {{353, 12}, {49, 17}} _NS:1535 @@ -596,11 +623,12 @@ 268 - {{246, 19}, {57, 17}} + {{296, 12}, {57, 17}} - + _NS:1535 + {250, 750} YES 68157504 @@ -617,16 +645,16 @@ 268 - {{257, 112}, {66, 17}} + {{284, 276}, {123, 17}} - + _NS:1535 YES 68157504 272630784 - Advanced + Bridge Information _NS:1535 @@ -638,10 +666,10 @@ 12 - {{207, 135}, {189, 5}} + {{207, 176}, {276, 5}} - + _NS:9 {0, 0} @@ -663,7 +691,7 @@ 268 - {{231, 85}, {118, 19}} + {{285, 194}, {130, 19}} @@ -688,7 +716,7 @@ -2147483380 - {{357, 87}, {16, 16}} + {{422, 196}, {16, 16}} @@ -697,14 +725,14 @@ 100 - {408, 249} + {495, 305} _NS:20 {250, 250} - {{0, 0}, {1680, 1028}} + {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES @@ -718,7 +746,7 @@ - + 256 @@ -726,7 +754,6 @@ 268 {{137, 19}, {53, 19}} - _NS:9 YES @@ -751,7 +778,6 @@ 268 {{179, 97}, {16, 16}} - _NS:945 28938 @@ -762,7 +788,6 @@ 268 {{17, 72}, {341, 17}} - _NS:1535 YES @@ -784,7 +809,6 @@ 268 {{198, 19}, {40, 19}} - _NS:9 YES @@ -816,7 +840,6 @@ {{163, 84}, {48, 48}} - _NS:9 YES @@ -841,7 +864,6 @@ -2147483380 {{124, 45}, {127, 19}} - _NS:9 YES @@ -863,12 +885,10 @@ {375, 133} - - _NS:21 - {{0, 0}, {1680, 1028}} + {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES @@ -882,7 +902,7 @@ - + 256 @@ -890,7 +910,6 @@ 268 {{17, 59}, {275, 17}} - _NS:1535 YES @@ -912,7 +931,6 @@ 268 {{17, 20}, {275, 17}} - _NS:1535 YES @@ -930,12 +948,10 @@ {309, 96} - - _NS:21 - {{0, 0}, {1680, 1028}} + {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES @@ -958,7 +974,7 @@ - + 256 @@ -972,10 +988,9 @@ 2322 - {423, 228} + {453, 228} - - + _NS:13 @@ -992,11 +1007,11 @@ - 423 + 453 1 - 11141 + 67120005 0 @@ -1032,12 +1047,12 @@ 6 {463, 10000000} + {438, 228} - {{1, 1}, {423, 228}} + {{1, 1}, {438, 228}} - _NS:11 @@ -1200,9 +1215,8 @@ wGw 256 - {{424, 1}, {15, 228}} + {{423, 1}, {16, 228}} - _NS:83 NO @@ -1215,9 +1229,9 @@ wGw -2147483392 {{-100, -100}, {87, 18}} - _NS:33 + YES NO 1 @@ -1228,10 +1242,9 @@ wGw {{20, 20}, {440, 230}} - - + _NS:9 - 133138 + 133266 @@ -1241,12 +1254,10 @@ wGw {480, 270} - - _NS:20 - {{0, 0}, {1680, 1028}} + {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES @@ -1509,6 +1520,22 @@ wGw 964 + + + useBlackAndWhiteMenuBarIconsCheckbox + + + + 1019 + + + + useBlackAndWhiteMenuBarIconsClicked: + + + + 1148 + delegate @@ -1588,44 +1615,28 @@ wGw 6 - - + + 6 0 - + 6 1 - 0.0 - - 1000 - - 6 - 24 - 2 - - - - 5 - 0 - - 6 - 1 - - 8 + 96 1000 - 6 - 24 + 3 + 9 3 - - - 10 + + + 11 0 - - 10 + + 11 1 0.0 @@ -1636,28 +1647,12 @@ wGw 24 2 - + 3 0 - - 3 - 1 - - 213 - - 1000 - - 3 - 9 - 3 - - - - 11 - 0 - 11 + 3 1 0.0 @@ -1671,7 +1666,7 @@ wGw 5 - 0 + 1 5 1 @@ -1680,19 +1675,19 @@ wGw 1000 - 3 - 9 + 9 + 40 3 - + 6 0 - + 6 1 - 70 + 57 1000 @@ -1700,12 +1695,12 @@ wGw 9 3 - - - 11 + + + 10 0 - - 11 + + 10 1 0.0 @@ -1716,12 +1711,28 @@ wGw 24 2 - + + + 6 + 0 + + 6 + 1 + + 145 + + 1000 + + 3 + 9 + 3 + + - 3 + 4 0 - - 3 + + 4 1 0.0 @@ -1736,7 +1747,7 @@ wGw 6 0 - + 6 1 @@ -1748,7 +1759,23 @@ wGw 9 3 - + + + 11 + 0 + + 11 + 1 + + 0.0 + + 1000 + + 6 + 24 + 2 + + 4 0 @@ -1756,7 +1783,7 @@ wGw 4 1 - 32 + 33 1000 @@ -1780,7 +1807,23 @@ wGw 24 2 - + + + 4 + 0 + + 4 + 1 + + 46 + + 1000 + + 3 + 9 + 3 + + 6 0 @@ -1788,7 +1831,7 @@ wGw 6 1 - 66 + 104 1000 @@ -1812,27 +1855,43 @@ wGw 24 2 - - - 4 + + + 3 0 - + 4 1 - - 45 + + 8 1000 - 3 - 9 + 6 + 24 3 - + + + 9 + 0 + + 9 + 1 + + 0.0 + + 1000 + + 6 + 24 + 2 + + 6 0 - + 6 1 @@ -1840,11 +1899,11 @@ wGw 1000 - 8 - 29 - 3 + 6 + 24 + 2 - + 5 0 @@ -1876,27 +1935,11 @@ wGw 24 2 - - - 3 - 0 - - 4 - 1 - - 8 - - 1000 - - 6 - 24 - 3 - - - + + 9 0 - + 9 1 @@ -1908,40 +1951,40 @@ wGw 24 2 - + - 9 + 3 0 - - 9 + + 3 1 - 0.0 + 92 1000 - 6 - 24 - 2 + 3 + 9 + 3 - - - 3 + + + 9 0 - - 4 + + 9 1 - - 8 + + 0.0 1000 6 24 - 3 + 2 - - + + 3 0 @@ -1956,44 +1999,44 @@ wGw 24 2 - - - 6 + + + 3 0 - - 6 + + 4 1 - 12 + 6 1000 - 8 - 29 + 6 + 24 3 - - - 5 + + + 4 0 - - 6 + + 4 1 - - 8 + + 100 1000 - 6 - 24 + 3 + 9 3 - - - 3 + + + 5 0 - - 4 + + 6 1 8 @@ -2004,27 +2047,27 @@ wGw 24 3 - - - 5 + + + 6 0 - + 6 1 - - 8 + + 62 1000 - 6 - 24 + 3 + 9 3 - - + + 3 0 - + 4 1 @@ -2036,28 +2079,28 @@ wGw 24 3 - - + + 5 0 - + 6 1 - - 8 + + -1 1000 - 6 - 24 + 9 + 40 3 - - - 3 + + + 5 0 - - 4 + + 6 1 8 @@ -2112,24 +2155,8 @@ wGw 1000 - 3 - 9 - 3 - - - - 5 - 0 - - 6 - 1 - - 8 - - 1000 - - 6 - 24 + 9 + 40 3 @@ -2196,6 +2223,54 @@ wGw 29 3 + + + 3 + 0 + + 3 + 1 + + 126 + + 1000 + + 3 + 9 + 3 + + + + 6 + 0 + + 6 + 1 + + 12 + + 1000 + + 8 + 29 + 3 + + + + 5 + 0 + + 6 + 1 + + 8 + + 1000 + + 6 + 24 + 3 + 4 @@ -2224,8 +2299,8 @@ wGw 1000 - 6 - 24 + 9 + 40 3 @@ -2292,7 +2367,7 @@ wGw 24 2 - + 4 0 @@ -2300,7 +2375,7 @@ wGw 4 1 - 41 + 40 1000 @@ -2360,22 +2435,23 @@ wGw - - + + + - + + - + - - - + + @@ -2848,7 +2924,7 @@ wGw 7 - 0 + 1 0 1 @@ -2857,8 +2933,8 @@ wGw 1000 - 3 - 9 + 9 + 40 1 @@ -2920,16 +2996,6 @@ wGw - - 444 - - - - - 460 - - - 464 @@ -2940,11 +3006,6 @@ wGw - - 497 - - - 498 @@ -2998,26 +3059,11 @@ wGw - - 534 - - - - - 571 - - - 572 - - 577 - - - 578 @@ -3028,11 +3074,6 @@ wGw - - 582 - - - 583 @@ -3227,7 +3268,8 @@ wGw 696 - + + 7 0 @@ -3235,15 +3277,14 @@ wGw 0 1 - 222 + 43 1000 - 3 - 9 + 9 + 40 1 - @@ -3252,16 +3293,6 @@ wGw - - 710 - - - - - 713 - - - 714 @@ -3291,66 +3322,11 @@ wGw - - 716 - - - - - 761 - - - - - 762 - - - - - 767 - - - - - 779 - - - 790 - - 594 - - - - - 592 - - - - - 771 - - - - - 769 - - - - - 768 - - - - - 770 - - - 816 @@ -3369,31 +3345,27 @@ wGw - - 833 - - - - - 835 - - - - - 836 - - - - - 837 - - - 843 + + + 7 + 0 + + 0 + 1 + + 130 + + 1000 + + 3 + 9 + 1 + @@ -3402,31 +3374,11 @@ wGw - - 850 - - - 857 - - 858 - - - - - 860 - - - - - 861 - - - 866 @@ -3617,26 +3569,6 @@ wGw - - 926 - - - - - 927 - - - - - 928 - - - - - 929 - - - 930 @@ -3805,6 +3737,199 @@ wGw + + 761 + + + + + 999 + + + + + + + + 1000 + + + + + 836 + + + + + 835 + + + + + 1018 + + + + + 444 + + + + + 716 + + + + + 1046 + + + + + 460 + + + + + 497 + + + + + 1059 + + + + + 1060 + + + + + 1065 + + + + + 1067 + + + + + 1071 + + + + + 1081 + + + + + 1083 + + + + + 1085 + + + + + 1094 + + + + + 1100 + + + + + 1102 + + + + + 1108 + + + + + 1109 + + + + + 1119 + + + + + 1120 + + + + + 1122 + + + + + 1123 + + + + + 1124 + + + + + 1125 + + + + + 1126 + + + + + 1127 + + + + + 1135 + + + + + 1138 + + + + + 1141 + + + + + 1142 + + + + + 1144 + + + + + 1147 + + + @@ -3812,11 +3937,41 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -3891,66 +4046,63 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - + + + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + - - - + - + + + + + + - - - - + com.apple.InterfaceBuilder.CocoaPlugin @@ -3987,13 +4139,11 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin @@ -4002,13 +4152,6 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -4033,18 +4176,15 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin + + + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -4075,10 +4215,6 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin - com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -4106,12 +4242,14 @@ wGw com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin - 964 + 1148 @@ -4127,6 +4265,7 @@ wGw id id id + id id @@ -4162,6 +4301,10 @@ wGw triggerTouchlink: id + + useBlackAndWhiteMenuBarIconsClicked: + id + viewDiscoveryLog: id @@ -4187,6 +4330,7 @@ wGw NSPanel NSButton NSTextField + NSButton NSTextField NSButton NSWindow @@ -4268,6 +4412,10 @@ wGw twitterLabel NSTextField + + useBlackAndWhiteMenuBarIconsCheckbox + NSButton + versionLabel NSTextField diff --git a/QuickHue/Images/bulb-black.png b/QuickHue/Images/bulb-black.png new file mode 100644 index 0000000000000000000000000000000000000000..94a8d3c62c9979a0af5b3c9876544cffcd339b31 GIT binary patch literal 1298 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDADajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XRMoSU}&gdW~OIo zVrph)sH0$HU}&Uo07PcGh9*{~W>!Y#3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qX zu2*iXmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~r2Ntn zTP2`NAzsKWfE$}v3=Jk=fazBx7U&!58GyV5Q|Rl9UukYGTy=3tP%6T`SPd=?sVqp< z4@xc0FD*(2MqHXQ$f^P>=c3falKi5O{QMkPC z!8&|>tvvIJOA_;vQ$1a5m4IgGWoD*WxmX&QxH-GIn7Eoa8ydPASr{0axEdK7m>4^m zo46X7!SuT1Czs}?=9R$orXcjX;?xUD47mkBn_W_iGRsm^+=}vZ6~Lah%Eav!C!FR% z^`_u7$oqKMp{VGe~ z$Xx2s+!S{w$Ex0nzbiJ&p+eO zLE%L;cJ)tw|C8i#yZ)8^*89`-T1{^+$gO@DyLVB)BzFy`0Rw-=$%%b+VLYHx%G1@) JWt~$(699zD&UXL+ literal 0 HcmV?d00001 diff --git a/QuickHue/Images/bulb-black@2x.png b/QuickHue/Images/bulb-black@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..024d8b0e2888c04f3ac8556c13d40629deda955e GIT binary patch literal 1292 zcmeAS@N?(olHy`uVBq!ia0vp^(m<@j!3HENemLp^DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XRMoSU}&gdW~OIo zVrph)sH0$HU}&Uo07PcGh9*{~W>!Y#3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qX zu2*iXmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~r2Ntn zTP2`NAzsKWfE$}v3=Jk=fazBx7U&!58GyV5Q|Rl9UukYGTy=3tP%6T`SPd=?sVqp< z4@xc0FD*(2MqHXQ$f^P>=c3falKi5O{QMkPC z!8&|>tvvIJOA_;vQ$1a5m4IgGWoD*WxmX&QxH-F7nz))c8ydPASr{0axEdK7m>4^m zo46X7!SuT1Czs}?=9R$orXch>;?xUD47mkBn_W_iGRsm^+=}vZ6~Lah%Eav!puM=< z0@0g-+btG2_38s1qYsK&q^O1o0n-nN2~W5{4m|0n<^j`t5int|Vo(I;3C2247srr_ zTSt}%ka1%^4N(J9>U%a${HjLde{vZ3mF8?vp=eK_`}`x-tj2Yf~I-aENY9Fq{KzZH)faxl zi!JAYil~ltr*Zk>qmde-k94NoU^&EdBwTaD3$3CRg3(PAvS;#Jraopg(z)fH=a}Q% zmTER7#8BOH!wZ#tT5X*jDtj7Qp6qK#nCg2VebR+X4HMS?bGjN765M>iR7q#I;|XUU z7q;`9N>^l*S3PxZInUJjAyPrnx+C|Lp;L4~mc^5N#*bA_zLsK1v+FMW5pHmJ1Ty=uzJ{O<=Mf+uHMLPPavGd{D{y-NQIb>f+=dX&KAX7c4vP z(7@f1&is*GV;-+)@`Bd$8n28D`EJiscW7W_@MKDf+fb;#5mY{Ty85}Sb4q9e0Jgfg AGynhq literal 0 HcmV?d00001 diff --git a/QuickHue/Images/bulb-white.png b/QuickHue/Images/bulb-white.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec7cbaf3de2c3a524a0c381da19f994c596d944 GIT binary patch literal 1303 zcmeAS@N?(olHy`uVBq!ia0vp^d_XM3!3HF=W8NDADajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XRMoSU}&gdW~OIo zVrph)sH0$HU}&Uo07PcGh9*{~W>!Y#3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qX zu2*iXmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~r2Ntn zTP2`NAzsKWfE$}v3=Jk=fazBx7U&!58GyV5Q|Rl9UukYGTy=3tP%6T`SPd=?sVqp< z4@xc0FD*(2MqHXQ$f^P>=c3falKi5O{QMkPC z!8&|>tvvIJOA_;vQ$1a5m4IgGWoD*WIa)fq7#O=bnz)*{7#g}7Sr{0axEdK7m>4^m zo46X7!SuT1Czs}?=9R$orXch>;nWLC47mkBn_W_iGRsm^+=}vZ6~Lah%Eav!3!LUb z^`_uxP+K0Z2Ge<}Wg{DsZ!akgLc&db@d>mI#!=~4jK zwIq9uD_%#Pf+iK3cM4tGR+)I>&AZR%&TL|IY2iIM@qyrKrz!uFA1&y)Ve9foPpOJ` zQ(Wsq)5CkWwcVK5+1xF8oNG~S&kLK!VHb^0U731UOe)l8$((^CA{uDSr z1<%~X^wgl##FWaylc_cg49qH-ArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XRMoSU}&gdW~OIo zVrph)sH0$HU}&Uo07PcGh9*{~W>!Y#3Q(W~w5=#5%__*n4QdyVXRDM^Qc_^0uU}qX zu2*iXmtT~wZ)j<02{OaTNEfI=x41H|B(Xv_uUHvof=g;~a#3bMNoIbY0?5R~r2Ntn zTP2`NAzsKWfE$}v3=Jk=fazBx7U&!58GyV5Q|Rl9UukYGTy=3tP%6T`SPd=?sVqp< z4@xc0FD*(2MqHXQ$f^P>=c3falKi5O{QMkPC z!8&|>tvvIJOA_;vQ$1a5m4IgGWoD*WIa)fq7#O>mnYfy`7#g}7Sr{0axEdK7m>4^m zo46X7!SuT1Czs}?=9R$orXchJ)#18Iua(|Zvx zVb4*k-NL}Y*x~8o7*cWT&Ge04%#H$W`KHs%GD9{@(Pr^ndC0cORYWIu%l5O6T}+m2 z6!6p!do)#xLyALeky2CZ-NxrbSX{kwhl=1t4wwY|$yjW3G4 z_3xS(FI{qwBjrrT{H?AVGtO(>U?|^q>qPox(bZlr7*fLWB>O*#-DcYPb^Dy-|GF~0 zg6idX4mu_1?P-*#@f1o~-zIQV>9|1Z<84mM-uu{TL?0915EA@G*hDp{r0rQ&S8a1! ziln{Bw2!g7(pNS`Xoh@#toJusD!(Qo2AdD-qC+G<3`nk0Bz~xV&Wxn%=`8$ot^0#vn}j^-hVq*|4Rp-{Z9@! cT*o58a9i+(=Yjvb!ayaJr>mdKI;Vst0Oak