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 0000000..94a8d3c Binary files /dev/null and b/QuickHue/Images/bulb-black.png differ diff --git a/QuickHue/Images/bulb-black@2x.png b/QuickHue/Images/bulb-black@2x.png new file mode 100644 index 0000000..024d8b0 Binary files /dev/null and b/QuickHue/Images/bulb-black@2x.png differ diff --git a/QuickHue/Images/bulb-white.png b/QuickHue/Images/bulb-white.png new file mode 100644 index 0000000..2ec7cba Binary files /dev/null and b/QuickHue/Images/bulb-white.png differ diff --git a/QuickHue/Images/bulb-white@2x.png b/QuickHue/Images/bulb-white@2x.png new file mode 100644 index 0000000..e9d8268 Binary files /dev/null and b/QuickHue/Images/bulb-white@2x.png differ