diff --git a/.gitignore b/.gitignore index 05096f66..6bddb33e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,4 @@ xcuserdata #Pods/ # Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build +Carthage/Build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..73aa1701 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Carthage/Checkouts/CocoaAsyncSocket"] + path = Carthage/Checkouts/CocoaAsyncSocket + url = https://github.com/robbiehanson/CocoaAsyncSocket.git diff --git a/Cartfile b/Cartfile new file mode 100644 index 00000000..aa4de116 --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "robbiehanson/CocoaAsyncSocket" ~> 7.4.3 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 00000000..dde47a85 --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1 @@ +github "robbiehanson/CocoaAsyncSocket" "7.4.3" diff --git a/Carthage/Checkouts/CocoaAsyncSocket b/Carthage/Checkouts/CocoaAsyncSocket new file mode 160000 index 00000000..f34134f7 --- /dev/null +++ b/Carthage/Checkouts/CocoaAsyncSocket @@ -0,0 +1 @@ +Subproject commit f34134f7d14125370caf54b45dcb32d77b3ce38c diff --git a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.h b/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.h deleted file mode 100755 index 67bb57c0..00000000 --- a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.h +++ /dev/null @@ -1,1197 +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 -#import - -#include // AF_INET, AF_INET6 - -@class GCDAsyncReadPacket; -@class GCDAsyncWritePacket; -@class GCDAsyncSocketPreBuffer; - -extern NSString *const GCDAsyncSocketException; -extern NSString *const GCDAsyncSocketErrorDomain; - -extern NSString *const GCDAsyncSocketQueueName; -extern NSString *const GCDAsyncSocketThreadName; - -extern NSString *const GCDAsyncSocketManuallyEvaluateTrust; -#if TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketUseCFStreamForTLS; -#endif -#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName -#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates -#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer -extern NSString *const GCDAsyncSocketSSLPeerID; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMin; -extern NSString *const GCDAsyncSocketSSLProtocolVersionMax; -extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart; -extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord; -extern NSString *const GCDAsyncSocketSSLCipherSuites; -#if !TARGET_OS_IPHONE -extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; -#endif - -#define GCDAsyncSocketLoggingContext 65535 - - -typedef NS_ENUM(NSInteger, 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 -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#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 - -@property (atomic, weak, readwrite) id delegate; -#if OS_OBJECT_USE_OBJC -@property (atomic, strong, readwrite) dispatch_queue_t delegateQueue; -#else -@property (atomic, assign, readwrite) dispatch_queue_t delegateQueue; -#endif - -- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr; -- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - -/** - * If you are setting the delegate to nil within the delegate's dealloc method, - * you may need to use the synchronous versions below. - **/ -- (void)synchronouslySetDelegate:(id)delegate; -- (void)synchronouslySetDelegateQueue:(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. - **/ - -@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled; -@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled; - -@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6; - -/** - * User data allows you to associate arbitrary information with the socket. - * This data is not used internally by socket in any way. - **/ -@property (atomic, strong, readwrite) id userData; - -#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; - -/** - * Tells the socket to begin listening and accepting connections on the unix domain at the given url. - * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it, - * and the socket:didAcceptNewSocket: delegate method will be invoked. - * - * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) - **/ -- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; - -#pragma mark Connecting - -/** - * 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; -/** - * Connects to the unix domain socket at the given url, using the specified timeout. - */ -- (BOOL)connectToUrl:(NSURL *)url 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. - **/ -@property (atomic, readonly) BOOL isDisconnected; -@property (atomic, readonly) BOOL isConnected; - -/** - * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. - * The host will be an IP address. - **/ -@property (atomic, readonly) NSString *connectedHost; -@property (atomic, readonly) uint16_t connectedPort; -@property (atomic, readonly) NSURL *connectedUrl; - -@property (atomic, readonly) NSString *localHost; -@property (atomic, readonly) uint16_t localPort; - -/** - * Returns the local or remote address to which this socket is connected, - * specified as a sockaddr structure wrapped in a NSData object. - * - * @seealso connectedHost - * @seealso connectedPort - * @seealso localHost - * @seealso localPort - **/ -@property (atomic, readonly) NSData *connectedAddress; -@property (atomic, readonly) NSData *localAddress; - -/** - * Returns whether the socket is IPv4 or IPv6. - * An accepting socket may be both. - **/ -@property (atomic, readonly) BOOL isIPv4; -@property (atomic, readonly) BOOL isIPv6; - -/** - * Returns whether or not the socket has been secured via SSL/TLS. - * - * See also the startTLS method. - **/ -@property (atomic, readonly) 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 available TOP-LEVEL KEYS are: - * - * - GCDAsyncSocketManuallyEvaluateTrust - * The value must be of type NSNumber, encapsulating a BOOL value. - * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer. - * Instead it will pause at the moment evaulation would typically occur, - * and allow us to handle the security evaluation however we see fit. - * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef. - * - * Note that if you set this option, then all other configuration keys are ignored. - * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method. - * - * For more information on trust evaluation see: - * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation - * https://developer.apple.com/library/ios/technotes/tn2232/_index.html - * - * If unspecified, the default value is NO. - * - * - GCDAsyncSocketUseCFStreamForTLS (iOS only) - * The value must be of type NSNumber, encapsulating a BOOL value. - * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption. - * This gives us more control over the security protocol (many more configuration options), - * plus it allows us to optimize things like sys calls and buffer allocation. - * - * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption - * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket - * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property - * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method. - * - * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket, - * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty. - * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings. - * - * If unspecified, the default value is NO. - * - * ==== The available CONFIGURATION KEYS are: - * - * - kCFStreamSSLPeerName - * The value must be of type NSString. - * It should match the name in the X.509 certificate given by the remote party. - * See Apple's documentation for SSLSetPeerDomainName. - * - * - kCFStreamSSLCertificates - * The value must be of type NSArray. - * See Apple's documentation for SSLSetCertificate. - * - * - kCFStreamSSLIsServer - * The value must be of type NSNumber, encapsulationg a BOOL value. - * See Apple's documentation for SSLCreateContext for iOS. - * This is optional for iOS. If not supplied, a NO value is the default. - * This is not needed for Mac OS X, and the value is ignored. - * - * - GCDAsyncSocketSSLPeerID - * The value must be of type NSData. - * You must set this value if you want to use TLS session resumption. - * See Apple's documentation for SSLSetPeerID. - * - * - GCDAsyncSocketSSLProtocolVersionMin - * - GCDAsyncSocketSSLProtocolVersionMax - * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value. - * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax. - * See also the SSLProtocol typedef. - * - * - GCDAsyncSocketSSLSessionOptionFalseStart - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionFalseStart. - * - * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord - * The value must be of type NSNumber, encapsulating a BOOL value. - * See Apple's documentation for kSSLSessionOptionSendOneByteRecord. - * - * - GCDAsyncSocketSSLCipherSuites - * The values must be of type NSArray. - * Each item within the array must be a NSNumber, encapsulating - * See Apple's documentation for SSLSetEnabledCiphers. - * See also the SSLCipherSuite typedef. - * - * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only) - * The value must be of type NSData. - * See Apple's documentation for SSLSetDiffieHellmanParams. - * - * ==== The following UNAVAILABLE KEYS are: (with throw an exception) - * - * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsAnyRoot - * - * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredRoots - * - * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetAllowsExpiredCerts - * - * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE) - * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust). - * Corresponding deprecated method: SSLSetEnableCertVerify - * - * - kCFStreamSSLLevel (UNAVAILABLE) - * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead. - * Corresponding deprecated method: SSLSetProtocolVersionEnabled - * - * - * Please refer to Apple's documentation for corresponding SSLFunctions. - * - * If you pass in nil or an empty dictionary, the default settings will be used. - * - * IMPORTANT SECURITY NOTE: - * The default settings will check to make sure the remote party's certificate is signed by a - * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. - * However it will not verify the name on the certificate unless you - * 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". - * - * You can also perform additional validation in socketDidSecure. - **/ -- (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. - **/ -@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream; - -/** - * 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 - -/** - * 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; - -#pragma mark Utilities - -/** - * The address lookup utility used by the class. - * This method is synchronous, so it's recommended you use it on a background thread/queue. - * - * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6. - * - * @returns - * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo. - * The addresses are specifically for TCP connections. - * You can filter the addresses, if needed, using the other utility methods provided by the class. - **/ -+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr; - -/** - * Extracting host and port information from raw address data. - **/ - -+ (NSString *)hostFromAddress:(NSData *)address; -+ (uint16_t)portFromAddress:(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:(sa_family_t *)afPtr 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 connects and is ready for reading and writing. - * The host parameter will be an IP address, not a DNS name. - **/ -- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url; - -/** - * Called when a socket has completed reading the requested data into memory. - * Not called if there is an error. - **/ -- (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, - * then an invocation of this delegate method will be enqueued on the delegateQueue - * before the disconnect method returns. - * - * Note: If the GCDAsyncSocket instance is deallocated while it is still connected, - * and the delegate is not also deallocated, then this method will be invoked, - * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.) - * This is a generally rare, but is possible if one writes code like this: - * - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * In this case it may preferrable to nil the delegate beforehand, like this: - * - * asyncSocket.delegate = nil; // Don't invoke my delegate method - * asyncSocket = nil; // I'm implicitly disconnecting the socket - * - * Of course, this depends on how your state machine is configured. - **/ -- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err; - -/** - * 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; - -/** - * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to. - * - * This is only called if startTLS is invoked with options that include: - * - GCDAsyncSocketManuallyEvaluateTrust == YES - * - * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer. - * - * Note from Apple's documentation: - * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain, - * [it] might block while attempting network access. You should never call it from your main thread; - * call it only from within a function running on a dispatch queue or on a separate thread. - * - * Thus this method uses a completionHandler block rather than a normal return value. - * The completionHandler block is thread-safe, and may be invoked from a background queue/thread. - * It is safe to invoke the completionHandler block even if the socket has been closed. - **/ -- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust -completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler; - -@end diff --git a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.m b/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.m deleted file mode 100755 index 248118f8..00000000 --- a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncSocket.m +++ /dev/null @@ -1,8239 +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 -#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 - - -#ifndef GCDAsyncSocketLoggingEnabled -#define GCDAsyncSocketLoggingEnabled 0 -#endif - -#if GCDAsyncSocketLoggingEnabled - -// 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 GCDAsyncSocketLoggingContext - -#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) - -#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__) - -#ifndef GCDAsyncSocketLogLevel -#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE -#endif - -// Log levels : off, error, warn, info, verbose -static const int logLevel = GCDAsyncSocketLogLevel; - -#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"; - -NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust"; -#if TARGET_OS_IPHONE -NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS"; -#endif -NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID"; -NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin"; -NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax"; -NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart"; -NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord"; -NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites"; -#if !TARGET_OS_IPHONE -NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters"; -#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 - kDealloc = 1 << 16, // If set, the socket is being deallocated -#if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available -#endif -}; - -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 - - - static uint64_t cfstreamThreadRetainCount; // setup & teardown - static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown -#endif - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#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 = [self availableSpace]; - - 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 = [self availableBytes]; -} - -- (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 - preBuffer); -} - -- (uint8_t *)writeBuffer -{ - return writePointer; -} - -- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr -{ - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; -} - -- (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 -{ - uint32_t flags; - uint16_t config; - - __weak id delegate; - dispatch_queue_t delegateQueue; - - int socket4FD; - int socket6FD; - int socketUN; - NSURL *socketUrl; - int stateIndex; - NSData * connectInterface4; - NSData * connectInterface6; - NSData * connectInterfaceUN; - - dispatch_queue_t socketQueue; - - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t acceptUNSource; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; - - NSMutableArray *readQueue; - NSMutableArray *writeQueue; - - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; - - unsigned long socketFDBytesAvailable; - - GCDAsyncSocketPreBuffer *preBuffer; - -#if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; -#endif - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; - OSStatus lastSSLHandshakeError; - - void *IsOnSocketQueueOrTargetQueueKey; - - id userData; -} - -- (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 !OS_OBJECT_USE_OBJC - if (dq) dispatch_retain(dq); - #endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - socketUrl = nil; - stateIndex = 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 !OS_OBJECT_USE_OBJC - 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); - - // Set dealloc flag. - // This is used by closeWithError to ensure we don't accidentally retain ourself. - flags |= kDealloc; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - 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)setIPv4PreferredOverIPv6:(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; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event4Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - - #pragma clang diagnostic pop - }}); - - - dispatch_source_set_cancel_handler(accept4Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - - #pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(accept4Source); - } - - if (enableIPv6) - { - accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - int socketFD = socket6FD; - dispatch_source_t acceptSource = accept6Source; - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"event6Block"); - - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - - #pragma clang diagnostic pop - }}); - - dispatch_source_set_cancel_handler(accept6Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); - #endif - - LogVerbose(@"close(socket6FD)"); - close(socketFD); - - #pragma clang diagnostic pop - }); - - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(accept6Source); - } - - 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)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr; -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - // CreateSocket Block - // This block will be invoked within the dispatch block below. - - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - - int socketFD = socket(domain, SOCK_STREAM, 0); - - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self 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; - } - - 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]; - - // Remove a previous socket - - NSError *error = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; - if ([fileManager fileExistsAtPath:url.path]) { - if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) { - NSString *msg = @"Could not remove previous unix domain socket at given url."; - err = [self otherError:msg]; - - return_from_block; - } - } - - // Resolve interface from description - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; - err = [self badParamError:msg]; - - return_from_block; - } - - // Create sockets, configure, bind, and listen - - LogVerbose(@"Creating unix domain socket"); - socketUN = createSocket(AF_UNIX, interface); - - if (socketUN == SOCKET_NULL) - { - return_from_block; - } - - socketUrl = url; - - // Create accept sources - - acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketUN, 0, socketQueue); - - int socketFD = socketUN; - dispatch_source_t acceptSource = acceptUNSource; - - dispatch_source_set_event_handler(acceptUNSource, ^{ @autoreleasepool { - - LogVerbose(@"eventUNBlock"); - - 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(acceptUNSource, ^{ - -#if NEEDS_DISPATCH_RETAIN_RELEASE - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); -#endif - - LogVerbose(@"close(socket4FD)"); - close(socketFD); - }); - - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(acceptUNSource); - - 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(); - - int socketType; - int childSocketFD; - NSData *childSocketAddress; - - if (parentSocketFD == socket4FD) - { - socketType = 0; - - 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) - { - socketType = 1; - - 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]; - } - else // if (parentSocketFD == socketUN) - { - socketType = 2; - - struct sockaddr_un addr; - socklen_t addrLen = sizeof(addr); - - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } - - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - - // Enable non-blocking IO on the socket - - 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 = [[[self class] alloc] initWithDelegate:theDelegate - delegateQueue:delegateQueue - socketQueue:childSocketQueue]; - - if (socketType == 0) - acceptedSocket->socket4FD = childSocketFD; - else if (socketType == 1) - acceptedSocket->socket6FD = childSocketFD; - else - acceptedSocket->socketUN = 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 !OS_OBJECT_USE_OBJC - 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)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterfaceUN = interface; - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; -} - -- (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 *preConnectErr = 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."; - preConnectErr = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithInterface:interface error:&preConnectErr]) - { - 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. - - NSString *hostCpy = [host copy]; - - int aStateIndex = stateIndex; - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - NSError *lookupErr = nil; - NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - if (lookupErr) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didFail:lookupErr]; - }}); - } - else - { - NSData *address4 = nil; - NSData *address6 = nil; - - for (NSData *address in addresses) - { - if (!address4 && [[self class] isIPv4Address:address]) - { - address4 = address; - } - else if (!address6 && [[self class] isIPv6Address:address]) - { - address6 = address; - } - } - - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } - - #pragma clang diagnostic pop - }}); - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - - if (errPtr) *errPtr = preConnectErr; - 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; -} - -- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; -{ - LogTrace(); - - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - // Check for problems with host parameter - - if ([url.path length] == 0) - { - NSString *msg = @"Invalid unix domain socket url."; - err = [self badParamError:msg]; - - return_from_block; - } - - // Run through standard pre-connect checks - - if (![self preConnectWithUrl:url error:&err]) - { - return_from_block; - } - - // We've made it past all the checks. - // It's time to start the connection process. - - flags |= kSocketStarted; - - // Start the normal connection process - - NSError *connectError = nil; - if (![self connectWithAddressUN:connectInterfaceUN error:&connectError]) - { - [self closeWithError:connectError]; - - return_from_block; - } - - [self startConnectTimeout:timeout]; - - result = YES; - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (result == NO) - { - if (errPtr) - *errPtr = err; - } - - return result; -} - -- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(address4 || address6, @"Expected at least one valid address"); - - if (aStateIndex != stateIndex) - { - 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)aStateIndex didFail:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - 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 aStateIndex = stateIndex; - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - if (result == 0) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf didConnect:aStateIndex]; - }}); - } - else - { - NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { - - [strongSelf didNotConnect:aStateIndex error:error]; - }}); - } - - #pragma clang diagnostic pop - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // Create the socket - - int socketFD; - - LogVerbose(@"Creating unix domain socket"); - - socketUN = socket(AF_UNIX, SOCK_STREAM, 0); - - socketFD = socketUN; - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return NO; - } - - // Bind the socket to the desired interface (if needed) - - LogVerbose(@"Binding socket..."); - - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - -// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; -// -// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); -// if (result != 0) -// { -// if (errPtr) -// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; -// -// return NO; -// } - - // Prevent SIGPIPE signals - - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - - // Start the connection process in a background queue - - int aStateIndex = stateIndex; - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ - - const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; - int result = connect(socketFD, addr, addr->sa_len); - if (result == 0) - { - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didConnect:aStateIndex]; - }}); - } - else - { - // TODO: Bad file descriptor - perror("connect"); - NSError *error = [self errnoErrorWithReason:@"Error in connect() function"]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - [self didNotConnect:aStateIndex error:error]; - }}); - } - }); - - LogVerbose(@"Connecting..."); - - return YES; -} - -- (void)didConnect:(int)aStateIndex -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - 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 stateIndex. - aStateIndex = stateIndex; - #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 (aStateIndex != stateIndex) - { - // 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]; - NSURL *url = [self connectedUrl]; - - __strong id theDelegate = delegate; - - if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) - { - SetupStreamsPart1(); - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToHost:host port:port]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) - { - SetupStreamsPart1(); - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didConnectToUrl:url]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } - - // Get the connected socket - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - // 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)aStateIndex error:(NSError *)error -{ - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - if (aStateIndex != stateIndex) - { - 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); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doConnectTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(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 stateIndex. - // This will prevent us from processing results from any related background asynchronous operations. - // - // Note: This should be called from close method even if connectTimer is NULL. - // This is because one might disconnect a socket prior to a successful connection which had no timeout. - - stateIndex++; - - 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 - - [sslPreBuffer reset]; - sslErrCode = lastSSLHandshakeError = 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 || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - CFRelease(sslContext); - #else - SSLDisposeContext(sslContext); - #endif - - sslContext = NULL; - } - - // 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 && !acceptUNSource && !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; - } - - if (socketUN != SOCKET_NULL) - { - LogVerbose(@"close(socketUN)"); - close(socketUN); - socketUN = SOCKET_NULL; - unlink(socketUrl.path.fileSystemRepresentation); - socketUrl = nil; - } - } - 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 (acceptUNSource) - { - LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); - dispatch_source_cancel(acceptUNSource); - - // We never suspend acceptUNSource - - acceptUNSource = 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; - socketUN = SOCKET_NULL; - } - - // If the client has passed the connect/accept method, then the connection has at least begun. - // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; - BOOL isDeallocating = (flags & kDealloc) ? YES : NO; - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - sslWriteCachedLength = 0; - - if (shouldCallDelegate) - { - __strong id theDelegate = delegate; - __strong id theSelf = isDeallocating ? nil : self; - - if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:theSelf 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; - } -} - -- (NSURL *)connectedUrl -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socketUN != SOCKET_NULL) - return [self connectedUrlFromSocketUN:socketUN]; - - return nil; - } - else - { - __block NSURL *result = nil; - - dispatch_sync(socketQueue, ^{ @autoreleasepool { - - if (socketUN != SOCKET_NULL) - result = [self connectedUrlFromSocketUN:socketUN]; - }}); - - 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]; -} - -- (NSURL *)connectedUrlFromSocketUN:(int)socketFD -{ - struct sockaddr_un sockaddr; - socklen_t sockaddrlen = sizeof(sockaddr); - - if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) - { - return 0; - } - return [[self class] urlFromSockaddrUN:&sockaddr]; -} - -- (NSString *)localHostFromSocket4:(int)socketFD -{ - struct sockaddr_in sockaddr4; - 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; -} - -- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url; -{ - NSString *path = url.path; - if (path.length == 0) { - return nil; - } - - struct sockaddr_un nativeAddr; - nativeAddr.sun_family = AF_UNIX; - strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path)); - nativeAddr.sun_len = SUN_LEN(&nativeAddr); - NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; - - return interface; -} - -- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD -{ - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); - - // Setup event handlers - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"readEventBlock"); - - strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable); - - if (strongSelf->socketFDBytesAvailable > 0) - [strongSelf doReadData]; - else - [strongSelf doReadEOF]; - - #pragma clang diagnostic pop - }}); - - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - LogVerbose(@"writeEventBlock"); - - strongSelf->flags |= kSocketCanAcceptBytes; - [strongSelf doWriteData]; - - #pragma clang diagnostic pop - }}); - - // Setup cancel handlers - - __block int socketFDRefCount = 2; - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; - #endif - - dispatch_source_set_cancel_handler(readSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"readCancelBlock"); - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - - #pragma clang diagnostic pop - }); - - dispatch_source_set_cancel_handler(writeSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"writeCancelBlock"); - - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); - #endif - - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } - - #pragma clang diagnostic pop - }); - - // 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)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return YES; - } - - #endif - - return NO; -} - -- (BOOL)usingSecureTransportForTLS -{ - // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) - - #if TARGET_OS_IPHONE - - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - - return NO; - } - - #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 - - __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); - } -} - -- (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 = NO; - unsigned long estimatedBytesAvailable = 0; - - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - - #endif - } - else - { - 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); - } - - 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] && lastSSLHandshakeError == errSSLWouldBlock) - { - // We are in the process of a SSL Handshake. - // We were waiting for incoming data which has just arrived. - - [self ssl_continueSSLHandshake]; - } - } - 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 occurred - - 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 read via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && hasBytesAvailable) - { - NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - - BOOL readIntoPreBuffer = NO; - uint8_t *buffer = NULL; - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Using CFStream, rather than SecureTransport, for TLS - - NSUInteger defaultReadLength = (1024 * 32); - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - 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 - { - // Using SecureTransport for TLS - // - // We know: - // - how many bytes are available on the socket - // - how many encrypted bytes are sitting in the sslPreBuffer - // - how many decypted bytes are sitting in the sslContext - // - // But we do NOT know: - // - how many encypted bytes are sitting in the sslContext - // - // So we play the regular game of using an upper bound instead. - - NSUInteger defaultReadLength = (1024 * 32); - - if (defaultReadLength < estimatedBytesAvailable) { - defaultReadLength = estimatedBytesAvailable + (1024 * 16); - } - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // 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)loop_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(). - } - } - else - { - // Normal socket operation - - NSUInteger bytesToRead; - - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - 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 - - NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToCopy]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // 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 && 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 - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - 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 = NO; - 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]) - { - error = [self sslError:errSSLClosedAbort]; - } - } - 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) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - 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 - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - } - 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 = nil; - - 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]; - } - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) - { - 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); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doReadTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(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; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - 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, (int64_t)(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] && lastSSLHandshakeError == errSSLWouldBlock) - { - // 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]; - } - } - 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 - { - // 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) - { - const size_t sslMaxBytesToWrite = 32768; - size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); - 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) - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - 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) - { - dispatch_async(socketQueue, ^{ @autoreleasepool{ - - [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 - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) - { - 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."); - - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - 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); - - __weak GCDAsyncSocket *weakSelf = self; - - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; - - [strongSelf doWriteTimeout]; - - #pragma clang diagnostic pop - }}); - - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); - - #pragma clang diagnostic pop - }); - #endif - - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(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; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - 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, (int64_t)(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 useSecureTransport = YES; - - #if TARGET_OS_IPHONE - { - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; - if (value && [value boolValue]) - useSecureTransport = NO; - } - #endif - - if (useSecureTransport) - { - [self ssl_startTLS]; - } - else - { - #if TARGET_OS_IPHONE - [self cf_startTLS]; - #endif - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via SecureTransport -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (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 = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - 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) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - 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; - if (tlsPacket == nil) // Code to quiet the analyzer - { - NSAssert(NO, @"Logic error"); - - [self closeWithError:[self otherError:@"Logic error"]]; - return; - } - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - BOOL isServer = [[tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer] boolValue]; - - #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - { - 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 // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - { - 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; - } - - - BOOL shouldManuallyEvaluateTrust = [[tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust] boolValue]; - if (shouldManuallyEvaluateTrust) - { - if (isServer) - { - [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; - return; - } - - status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; - return; - } - - #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - - // Note from Apple's documentation: - // - // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. - // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the - // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus - // SSLSetEnableCertVerify is not available on that platform at all. - - status = SSLSetEnableCertVerify(sslContext, NO); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; - return; - } - - #endif - } - - // Configure SSLContext from given settings - // - // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLCertificates - // 3. GCDAsyncSocketSSLPeerID - // 4. GCDAsyncSocketSSLProtocolVersionMin - // 5. GCDAsyncSocketSSLProtocolVersionMax - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) - // - // Deprecated (throw error): - // 10. kCFStreamSSLAllowsAnyRoot - // 11. kCFStreamSSLAllowsExpiredRoots - // 12. kCFStreamSSLAllowsExpiredCertificates - // 13. kCFStreamSSLValidatesCertificateChain - // 14. kCFStreamSSLLevel - - id value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(__bridge 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; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; - return; - } - - // 2. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; - if ([value isKindOfClass:[NSArray class]]) - { - CFArrayRef certs = (__bridge CFArrayRef)value; - - status = SSLSetCertificate(sslContext, certs); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; - return; - } - - // 3. GCDAsyncSocketSSLPeerID - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *peerIdData = (NSData *)value; - - status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." - @" (You can convert strings to data using a method like" - @" [string dataUsingEncoding:NSUTF8StringEncoding])"); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; - return; - } - - // 4. GCDAsyncSocketSSLProtocolVersionMin - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (minProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMin(sslContext, minProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; - return; - } - - // 5. GCDAsyncSocketSSLProtocolVersionMax - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (maxProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMax(sslContext, maxProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; - return; - } - - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; - if ([value isKindOfClass:[NSNumber class]]) - { - status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [value boolValue]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; - return; - } - - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; - if ([value isKindOfClass:[NSNumber class]]) - { - status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [value boolValue]); - if (status != noErr) - { - [self closeWithError: - [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." - @" Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; - return; - } - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if ([value isKindOfClass:[NSArray class]]) - { - 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; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; - return; - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - - #if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; - return; - } - #endif - - // DEPRECATED checks - - // 10. kCFStreamSSLAllowsAnyRoot - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; - return; - } - - // 11. kCFStreamSSLAllowsExpiredRoots - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; - return; - } - - // 12. kCFStreamSSLValidatesCertificateChain - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; - return; - } - - // 13. kCFStreamSSLAllowsExpiredCertificates - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; - return; - } - - // 14. kCFStreamSSLLevel - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" - @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; - return; - } - - // Setup the sslPreBuffer - // - // 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 = lastSSLHandshakeError = 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. - // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the - // server and then call SSLHandshake again to resume the handshake or close the connection - // errSSLPeerBadCert SSL error. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - lastSSLHandshakeError = status; - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLPeerAuthCompleted) - { - LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); - - __block SecTrustRef trust = NULL; - status = SSLCopyPeerTrust(sslContext, &trust); - if (status != noErr) - { - [self closeWithError:[self sslError:status]]; - return; - } - - int aStateIndex = stateIndex; - dispatch_queue_t theSocketQueue = socketQueue; - - __weak GCDAsyncSocket *weakSelf = self; - - void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - dispatch_async(theSocketQueue, ^{ @autoreleasepool { - - if (trust) { - CFRelease(trust); - trust = NULL; - } - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf) - { - [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; - } - }}); - - #pragma clang diagnostic pop - }}; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; - }}); - } - else - { - if (trust) { - CFRelease(trust); - trust = NULL; - } - - NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," - @" but delegate doesn't implement socket:shouldTrustPeer:"; - - [self closeWithError:[self otherError:msg]]; - return; - } - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } -} - -- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex -{ - LogTrace(); - - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); - - // One of the following is true - // - the socket was disconnected - // - the startTLS operation timed out - // - the completionHandler was already invoked once - - return; - } - - // Increment stateIndex to ensure completionHandler can only be called once. - stateIndex++; - - if (shouldTrust) - { - NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError); - [self ssl_continueSSLHandshake]; - } - else - { - [self closeWithError:[self sslError:errSSLPeerBadCert]]; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via CFStream -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#if TARGET_OS_IPHONE - -- (void)cf_finishSSLHandshake -{ - LogTrace(); - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - 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)ignore:(id)_ -{} - -+ (void)startCFStreamThreadIfNeeded -{ - LogTrace(); - - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ - - cfstreamThreadRetainCount = 0; - cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL); - }); - - dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool { - - if (++cfstreamThreadRetainCount == 1) - { - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread) - object:nil]; - [cfstreamThread start]; - } - }}); -} - -+ (void)stopCFStreamThreadIfNeeded -{ - LogTrace(); - - // The creation of the cfstreamThread is relatively expensive. - // So we'd like to keep it available for recycling. - // However, there's a tradeoff here, because it shouldn't remain alive forever. - // So what we're going to do is use a little delay before taking it down. - // This way it can be reused properly in situations where multiple sockets are continually in flux. - - int delayInSeconds = 30; - dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - if (cfstreamThreadRetainCount == 0) - { - LogWarn(@"Logic error concerning cfstreamThread start / stop"); - return_from_block; - } - - if (--cfstreamThreadRetainCount == 0) - { - [cfstreamThread cancel]; // set isCancelled flag - - // wake up the thread - [[self class] performSelector:@selector(ignore:) - onThread:cfstreamThread - withObject:[NSNull null] - waitUntilDone:NO]; - - cfstreamThread = nil; - } - - #pragma clang diagnostic pop - }}); -} - -+ (void)cfstreamThread { @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(ignore:) - userInfo:nil - repeats:YES]; - - NSThread *currentThread = [NSThread currentThread]; - NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; - - BOOL isCancelled = [currentThread isCancelled]; - - while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) - { - isCancelled = [currentThread isCancelled]; - } - - LogInfo(@"CFStreamThread: Stopped"); -}} - -+ (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 = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - 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]; - [[self class] stopCFStreamThreadIfNeeded]; - - 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 occurred 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 - -- (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; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Class Utilities -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr -{ - LogTrace(); - - NSMutableArray *addresses = nil; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr4; - nativeAddr4.sin_len = sizeof(struct sockaddr_in); - nativeAddr4.sin_family = AF_INET; - nativeAddr4.sin_port = htons(port); - nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); - - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures - - NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - addresses = [NSMutableArray arrayWithCapacity:2]; - [addresses addObject:address4]; - [addresses addObject:address6]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - NSUInteger capacity = 0; - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { - capacity++; - } - } - - addresses = [NSMutableArray arrayWithCapacity:capacity]; - - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address. - // Wrap the native address structure, and add to results. - - NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address4]; - } - else if (res->ai_family == AF_INET6) - { - // Found IPv6 address. - // Wrap the native address structure, and add to results. - - NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address6]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (errPtr) *errPtr = error; - return addresses; -} - -+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 -{ - 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); -} - -+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr -{ - NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; - return [NSURL fileURLWithPath:path]; -} - -+ (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)isIPv4Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET) { - return YES; - } - } - - return NO; -} - -+ (BOOL)isIPv6Address:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; - - if (sockaddrX->sa_family == AF_INET6) { - return YES; - } - } - - return NO; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address -{ - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; -} - -+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address -{ - if ([address length] >= sizeof(struct sockaddr)) - { - 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]; - if (afPtr) *afPtr = AF_INET; - - 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]; - if (afPtr) *afPtr = AF_INET6; - - 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/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.h b/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.h deleted file mode 100755 index 7587965b..00000000 --- a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.h +++ /dev/null @@ -1,1007 +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 -#import -#import - -extern NSString *const GCDAsyncUdpSocketException; -extern NSString *const GCDAsyncUdpSocketErrorDomain; - -extern NSString *const GCDAsyncUdpSocketQueueName; -extern NSString *const GCDAsyncUdpSocketThreadName; - -typedef NS_ENUM(NSInteger, 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 -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -@class GCDAsyncUdpSocket; - -@protocol GCDAsyncUdpSocketDelegate -@optional - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection is successful. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; - -/** - * By design, UDP is a connectionless protocol, and connecting is not needed. - * However, you may optionally choose to connect to a particular host for reasons - * outlined in the documentation for the various connect methods listed above. - * - * This method is called if one of the connect methods are invoked, and the connection fails. - * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. -**/ -- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)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 - -/** - * 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, - * 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. - * - * 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 Reuse Port - -/** - * By default, only one socket can be bound to a given IP address + port at a time. - * To enable multiple processes to simultaneously bind to the same address+port, - * you need to enable this functionality in the socket. All processes that wish to - * use the address+port simultaneously must all enable reuse port on the socket - * bound to that port. - **/ -- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; - -#pragma mark Broadcast - -/** - * 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 remoteAddr - * 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 -/** - * 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 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 - diff --git a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.m b/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.m deleted file mode 100755 index 27c739d8..00000000 --- a/LivioHTTPServer/CocoaAsyncSocketGCD/GCDAsyncUdpSocket.m +++ /dev/null @@ -1,5430 +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 "GCDAsyncUdpSocket.h" - -#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 - -#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 () -{ -#if __has_feature(objc_arc_weak) - __weak id delegate; -#else - __unsafe_unretained id delegate; -#endif - 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; - - void *IsOnSocketQueueOrTargetQueueKey; - -#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 !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - dispatch_retain(socketQueue); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName 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); - - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; - - #if !OS_OBJECT_USE_OBJC - 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 = nil; - - 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 = NULL; - - dispatch_sync(socketQueue, ^{ - result = delegateQueue; - }); - - return result; - } -} - -- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously -{ - dispatch_block_t block = ^{ - - #if !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - 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 - - __block BOOL result = NO; - - dispatch_block_t block = ^{ - - result = ((config & kIPv4Disabled) == 0); - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (BOOL)isIPv4Preferred -{ - __block BOOL result = NO; - - dispatch_block_t block = ^{ - result = (config & kPreferIPv4) ? YES : NO; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - return result; -} - -- (void)setPreferIPv4 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config |= kPreferIPv4; - config &= ~kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setPreferIPv6 -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config &= ~kPreferIPv4; - config |= kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)setIPVersionNeutral -{ - dispatch_block_t block = ^{ - - LogTrace(); - - config &= ~kPreferIPv4; - config &= ~kPreferIPv6; - - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint16_t)maxReceiveIPv4BufferSize -{ - __block uint16_t result = 0; - - dispatch_block_t block = ^{ - - result = max4ReceiveSize; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (uint32_t)maxReceiveIPv6BufferSize -{ - __block uint32_t result = 0; - - dispatch_block_t block = ^{ - - result = max6ReceiveSize; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_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 Delegate Helpers -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (void)notifyDidConnectToAddress:(NSData *)anAddress -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) - { - NSData *address = [anAddress copy]; // In case param is NSMutableData - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didConnectToAddress:address]; - }}); - } -} - -- (void)notifyDidNotConnect:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didNotConnect:error]; - }}); - } -} - -- (void)notifyDidSendDataWithTag:(long)tag -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didSendDataWithTag:tag]; - }}); - } -} - -- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) - { - 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:); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:selector]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; - }}); - } -} - -- (void)notifyDidCloseWithError:(NSError *)error -{ - LogTrace(); - - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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 !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send4Source; - dispatch_source_t theReceiveSource = receive4Source; - #endif - - dispatch_source_set_cancel_handler(send4Source, ^{ - - LogVerbose(@"send4CancelBlock"); - - #if !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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 !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send6Source; - dispatch_source_t theReceiveSource = receive6Source; - #endif - - dispatch_source_set_cancel_handler(send6Source, ^{ - - LogVerbose(@"send6CancelBlock"); - - #if !OS_OBJECT_USE_OBJC - 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 !OS_OBJECT_USE_OBJC - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_INET6) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); - - return result; -} - -- (void)maybeUpdateCachedConnectedAddressInfo -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_IPV6, 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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Reuse port -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr -{ - __block BOOL result = NO; - __block NSError *err = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (![self preOp:&err]) - { - return_from_block; - } - - if ((flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } - - int value = flag ? 1 : 0; - if (socket4FD != SOCKET_NULL) - { - int error = setsockopt(socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - if (socket6FD != SOCKET_NULL) - { - int error = setsockopt(socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); - - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - result = YES; - } - - }}; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - - if (errPtr) - *errPtr = err; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Broadcast -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -- (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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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 !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); - #endif - } - - dispatch_block_t block = ^{ - - #if !OS_OBJECT_USE_OBJC - if (sendFilterQueue) dispatch_release(sendFilterQueue); - #endif - - sendFilterBlock = newFilterBlock; - sendFilterQueue = newFilterQueue; - sendFilterAsync = isAsynchronous; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -- (void)maybeDequeueSend -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"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 !OS_OBJECT_USE_OBJC - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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 !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); - #endif - } - - dispatch_block_t block = ^{ - - #if !OS_OBJECT_USE_OBJC - if (receiveFilterQueue) dispatch_release(receiveFilterQueue); - #endif - - receiveFilterBlock = newFilterBlock; - receiveFilterQueue = newFilterQueue; - receiveFilterAsync = isAsynchronous; - }; - - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - 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); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max4ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); - 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); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max6ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - 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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey), @"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_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Advanced -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * 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); -} - -- (void)performBlock:(dispatch_block_t)block -{ - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} - -- (int)socketFD -{ - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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_specific(IsOnSocketQueueOrTargetQueueKey)) - { - 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/LivioHTTPServer/LivioHTTPServer.xcodeproj/project.pbxproj b/LivioHTTPServer/LivioHTTPServer.xcodeproj/project.pbxproj index 170a2810..e1feed0f 100644 --- a/LivioHTTPServer/LivioHTTPServer.xcodeproj/project.pbxproj +++ b/LivioHTTPServer/LivioHTTPServer.xcodeproj/project.pbxproj @@ -7,15 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 27925B4C1D09DFA700D36FE0 /* CocoaAsyncSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27925B4B1D09DFA700D36FE0 /* CocoaAsyncSocket.framework */; }; + 27925B4F1D09DFF200D36FE0 /* CocoaAsyncSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27925B4B1D09DFA700D36FE0 /* CocoaAsyncSocket.framework */; }; + 27925B511D09DFF800D36FE0 /* CocoaAsyncSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27925B501D09DFF800D36FE0 /* CocoaAsyncSocket.framework */; }; + 27925B521D09DFFE00D36FE0 /* CocoaAsyncSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27925B501D09DFF800D36FE0 /* CocoaAsyncSocket.framework */; }; + 27925B541D09E01200D36FE0 /* CocoaAsyncSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27925B4B1D09DFA700D36FE0 /* CocoaAsyncSocket.framework */; }; 5D10E50A1BB0520200E9A728 /* LHSServerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D10E5091BB0520200E9A728 /* LHSServerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D1665101CEF5AE700CC4CA1 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650C1CEF5AE700CC4CA1 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D1665111CEF5AE700CC4CA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D16650D1CEF5AE700CC4CA1 /* GCDAsyncSocket.m */; }; - 5D1665121CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650E1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h */; }; - 5D1665131CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D16650F1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m */; }; - 5D1665141CEF5AF000CC4CA1 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650C1CEF5AE700CC4CA1 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D1665161CEF5AFB00CC4CA1 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650E1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h */; }; - 5D1665251CEF7F1A00CC4CA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D16650D1CEF5AE700CC4CA1 /* GCDAsyncSocket.m */; }; - 5D1665261CEF7F1A00CC4CA1 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D16650F1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m */; }; 5D1665271CEF7F1A00CC4CA1 /* NSData+LHSData.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D4834021A9E6A3900252386 /* NSData+LHSData.m */; }; 5D1665281CEF7F1A00CC4CA1 /* NSNumber+LHSNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D4834041A9E6A3900252386 /* NSNumber+LHSNumber.m */; }; 5D1665291CEF7F1A00CC4CA1 /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D4834061A9E6A3900252386 /* DDRange.m */; }; @@ -49,8 +46,6 @@ 5D1665461CEF7F6E00CC4CA1 /* LHSWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D48343A1A9E6A6D00252386 /* LHSWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D1665471CEF7F6E00CC4CA1 /* LHSConnectionConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D38109B1BA8927600B41AE3 /* LHSConnectionConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D1665481CEF7F6E00CC4CA1 /* LivioHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D4834471A9E6A8200252386 /* LivioHTTPServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D1665491CEF7F8900CC4CA1 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650C1CEF5AE700CC4CA1 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D16654A1CEF7F8900CC4CA1 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D16650E1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h */; }; 5D16654B1CEF7F8900CC4CA1 /* NSData+LHSData.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D4834011A9E6A3900252386 /* NSData+LHSData.h */; }; 5D16654C1CEF7F8900CC4CA1 /* NSNumber+LHSNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D4834031A9E6A3900252386 /* NSNumber+LHSNumber.h */; }; 5D16654D1CEF7F8900CC4CA1 /* DDRange.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D4834051A9E6A3900252386 /* DDRange.h */; }; @@ -162,11 +157,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 27925B4B1D09DFA700D36FE0 /* CocoaAsyncSocket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaAsyncSocket.framework; path = ../Carthage/Build/iOS/CocoaAsyncSocket.framework; sourceTree = ""; }; + 27925B501D09DFF800D36FE0 /* CocoaAsyncSocket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CocoaAsyncSocket.framework; path = ../Carthage/Build/Mac/CocoaAsyncSocket.framework; sourceTree = ""; }; 5D10E5091BB0520200E9A728 /* LHSServerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LHSServerDelegate.h; path = LivioHTTPServer/Core/LHSServerDelegate.h; sourceTree = SOURCE_ROOT; }; - 5D16650C1CEF5AE700CC4CA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = CocoaAsyncSocketGCD/GCDAsyncSocket.h; sourceTree = SOURCE_ROOT; }; - 5D16650D1CEF5AE700CC4CA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = CocoaAsyncSocketGCD/GCDAsyncSocket.m; sourceTree = SOURCE_ROOT; }; - 5D16650E1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = CocoaAsyncSocketGCD/GCDAsyncUdpSocket.h; sourceTree = SOURCE_ROOT; }; - 5D16650F1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncUdpSocket.m; path = CocoaAsyncSocketGCD/GCDAsyncUdpSocket.m; sourceTree = SOURCE_ROOT; }; 5D16651C1CEF7E9200CC4CA1 /* libLivioHTTPServer-Static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libLivioHTTPServer-Static.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5D38109B1BA8927600B41AE3 /* LHSConnectionConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LHSConnectionConfig.h; path = LivioHTTPServer/Core/LHSConnectionConfig.h; sourceTree = SOURCE_ROOT; }; 5D38109C1BA8927600B41AE3 /* LHSConnectionConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LHSConnectionConfig.m; path = LivioHTTPServer/Core/LHSConnectionConfig.m; sourceTree = SOURCE_ROOT; }; @@ -225,6 +218,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 27925B541D09E01200D36FE0 /* CocoaAsyncSocket.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,6 +226,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 27925B511D09DFF800D36FE0 /* CocoaAsyncSocket.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -240,6 +235,7 @@ buildActionMask = 2147483647; files = ( 5D3CD6C91AA0F4B300987CC1 /* LivioHTTPServer_Mac.framework in Frameworks */, + 27925B521D09DFFE00D36FE0 /* CocoaAsyncSocket.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -247,6 +243,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 27925B4C1D09DFA700D36FE0 /* CocoaAsyncSocket.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -255,29 +252,36 @@ buildActionMask = 2147483647; files = ( 5D48345A1A9E7B6900252386 /* LivioHTTPServer.framework in Frameworks */, + 27925B4F1D09DFF200D36FE0 /* CocoaAsyncSocket.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 5D16650A1CEF5ACA00CC4CA1 /* Libs */ = { + 27925B4D1D09DFAC00D36FE0 /* Frameworks */ = { isa = PBXGroup; children = ( - 5D16650B1CEF5AD000CC4CA1 /* CocoaAsyncSocket */, + 27925B531D09E00200D36FE0 /* Mac */, + 27925B4E1D09DFB300D36FE0 /* iOS */, ); - name = Libs; + name = Frameworks; sourceTree = ""; }; - 5D16650B1CEF5AD000CC4CA1 /* CocoaAsyncSocket */ = { + 27925B4E1D09DFB300D36FE0 /* iOS */ = { isa = PBXGroup; children = ( - 5D16650C1CEF5AE700CC4CA1 /* GCDAsyncSocket.h */, - 5D16650D1CEF5AE700CC4CA1 /* GCDAsyncSocket.m */, - 5D16650E1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h */, - 5D16650F1CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m */, + 27925B4B1D09DFA700D36FE0 /* CocoaAsyncSocket.framework */, ); - name = CocoaAsyncSocket; + name = iOS; + sourceTree = ""; + }; + 27925B531D09E00200D36FE0 /* Mac */ = { + isa = PBXGroup; + children = ( + 27925B501D09DFF800D36FE0 /* CocoaAsyncSocket.framework */, + ); + name = Mac; sourceTree = ""; }; 5D3CD6BF1AA0F4B300987CC1 /* LivioHTTPServer-Mac */ = { @@ -321,6 +325,7 @@ 5D4834551A9E7B6900252386 /* LivioHTTPServerTests */, 5D3CD6BF1AA0F4B300987CC1 /* LivioHTTPServer-Mac */, 5D3CD6CC1AA0F4B300987CC1 /* LivioHTTPServer-MacTests */, + 27925B4D1D09DFAC00D36FE0 /* Frameworks */, 5D4833981A9E574400252386 /* Products */, ); sourceTree = ""; @@ -340,7 +345,6 @@ 5D4833991A9E574400252386 /* LivioHTTPServer */ = { isa = PBXGroup; children = ( - 5D16650A1CEF5ACA00CC4CA1 /* Libs */, 5D4833B31A9E577000252386 /* Categories */, 5D4833BA1A9E577000252386 /* Mime */, 5D4833C11A9E577000252386 /* Responses */, @@ -497,8 +501,6 @@ 5D16654F1CEF7F9600CC4CA1 /* LHSMultipartMessageHeader.h in Headers */, 5D1665501CEF7F9600CC4CA1 /* LHSMultipartMessageHeaderField.h in Headers */, 5D1665511CEF7F9600CC4CA1 /* LHSResponse.h in Headers */, - 5D1665491CEF7F8900CC4CA1 /* GCDAsyncSocket.h in Headers */, - 5D16654A1CEF7F8900CC4CA1 /* GCDAsyncUdpSocket.h in Headers */, 5D16654B1CEF7F8900CC4CA1 /* NSData+LHSData.h in Headers */, 5D16654C1CEF7F8900CC4CA1 /* NSNumber+LHSNumber.h in Headers */, 5D16654D1CEF7F8900CC4CA1 /* DDRange.h in Headers */, @@ -520,7 +522,6 @@ 5D3CD6DC1AA0F56600987CC1 /* LHSMultipartMessageHeaderField.h in Headers */, 5D3CD6D91AA0F55900987CC1 /* DDRange.h in Headers */, 5D3CD6DE1AA0F57700987CC1 /* LHSAsyncFileResponse.h in Headers */, - 5D1665141CEF5AF000CC4CA1 /* GCDAsyncSocket.h in Headers */, 5D3CD6D71AA0F55200987CC1 /* NSData+LHSData.h in Headers */, 5D3CD6DA1AA0F55E00987CC1 /* LHSMultipartFormDataParser.h in Headers */, 5D3CD6E21AA0F57700987CC1 /* LHSFileResponse.h in Headers */, @@ -530,7 +531,6 @@ 5D77FA401B5D3ACF0013F9A7 /* LHSWebSocketDelegate.h in Headers */, 5D3CD6C31AA0F4B300987CC1 /* LivioHTTPServer-Mac.h in Headers */, 5D3CD6E71AA0F58C00987CC1 /* LHSServer.h in Headers */, - 5D1665161CEF5AFB00CC4CA1 /* GCDAsyncUdpSocket.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -545,10 +545,8 @@ 5D48342D1A9E6A6100252386 /* LHSFileResponse.h in Headers */, 5D4834291A9E6A6100252386 /* LHSDynamicFileResponse.h in Headers */, 5D4834131A9E6A4500252386 /* LHSMultipartFormDataParser.h in Headers */, - 5D1665121CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.h in Headers */, 5D4834451A9E6A6D00252386 /* LHSWebSocket.h in Headers */, 5D48343E1A9E6A6D00252386 /* LHSConnection.h in Headers */, - 5D1665101CEF5AE700CC4CA1 /* GCDAsyncSocket.h in Headers */, 5D4834401A9E6A6D00252386 /* LHSMessage.h in Headers */, 5D10E50A1BB0520200E9A728 /* LHSServerDelegate.h in Headers */, 5D4834251A9E6A6100252386 /* LHSAsyncFileResponse.h in Headers */, @@ -611,6 +609,7 @@ 5D3CD6C41AA0F4B300987CC1 /* Sources */, 5D3CD6C51AA0F4B300987CC1 /* Frameworks */, 5D3CD6C61AA0F4B300987CC1 /* Resources */, + 27925B4A1D09DF8B00D36FE0 /* ShellScript */, ); buildRules = ( ); @@ -648,6 +647,7 @@ 5D4834501A9E7B6900252386 /* Sources */, 5D4834511A9E7B6900252386 /* Frameworks */, 5D4834521A9E7B6900252386 /* Resources */, + 27925B491D09DF8400D36FE0 /* ShellScript */, ); buildRules = ( ); @@ -737,13 +737,42 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 27925B491D09DF8400D36FE0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/../Carthage/Build/iOS/CocoaAsyncSocket.framework", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; + 27925B4A1D09DF8B00D36FE0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/../Carthage/Build/Mac/CocoaAsyncSocket.framework", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/usr/local/bin/carthage copy-frameworks"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 5D1665181CEF7E9200CC4CA1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5D1665251CEF7F1A00CC4CA1 /* GCDAsyncSocket.m in Sources */, - 5D1665261CEF7F1A00CC4CA1 /* GCDAsyncUdpSocket.m in Sources */, 5D1665271CEF7F1A00CC4CA1 /* NSData+LHSData.m in Sources */, 5D1665281CEF7F1A00CC4CA1 /* NSNumber+LHSNumber.m in Sources */, 5D1665291CEF7F1A00CC4CA1 /* DDRange.m in Sources */, @@ -800,9 +829,7 @@ 5D48340A1A9E6A3900252386 /* NSNumber+LHSNumber.m in Sources */, 5D4834411A9E6A6D00252386 /* LHSMessage.m in Sources */, 5D48342A1A9E6A6100252386 /* LHSDynamicFileResponse.m in Sources */, - 5D1665131CEF5AE700CC4CA1 /* GCDAsyncUdpSocket.m in Sources */, 5D4834261A9E6A6100252386 /* LHSAsyncFileResponse.m in Sources */, - 5D1665111CEF5AE700CC4CA1 /* GCDAsyncSocket.m in Sources */, 5D38109E1BA8927600B41AE3 /* LHSConnectionConfig.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -836,6 +863,7 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; DEBUG_INFORMATION_FORMAT = dwarf; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/iOS"; GCC_NO_COMMON_BLOCKS = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; OTHER_LDFLAGS = "-ObjC"; @@ -850,6 +878,7 @@ CLANG_ANALYZER_NONNULL = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/iOS"; GCC_NO_COMMON_BLOCKS = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; OTHER_LDFLAGS = "-ObjC"; @@ -866,6 +895,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/Mac"; FRAMEWORK_VERSION = A; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -891,6 +921,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/Mac"; FRAMEWORK_VERSION = A; INFOPLIST_FILE = "LivioHTTPServer-Mac/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -910,6 +941,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", + "$(SRCROOT)/../Carthage/Build/Mac", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -932,6 +964,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", + "$(SRCROOT)/../Carthage/Build/Mac", ); INFOPLIST_FILE = "LivioHTTPServer-MacTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; @@ -1034,7 +1067,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Carthage/Build/iOS", + ); INFOPLIST_FILE = LivioHTTPServer/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1053,7 +1089,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Carthage/Build/iOS", + ); INFOPLIST_FILE = LivioHTTPServer/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1067,6 +1106,7 @@ 5D48345E1A9E7B6900252386 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/iOS"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1081,6 +1121,7 @@ 5D48345F1A9E7B6900252386 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/Build/iOS"; INFOPLIST_FILE = LivioHTTPServerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.livio.$(PRODUCT_NAME:rfc1034identifier)"; diff --git a/LivioHTTPServer/LivioHTTPServer/Core/Categories/NSData+LHSData.m b/LivioHTTPServer/LivioHTTPServer/Core/Categories/NSData+LHSData.m index 7e4e3177..504197e9 100644 --- a/LivioHTTPServer/LivioHTTPServer/Core/Categories/NSData+LHSData.m +++ b/LivioHTTPServer/LivioHTTPServer/Core/Categories/NSData+LHSData.m @@ -23,7 +23,7 @@ - (NSString *)hexString { const Byte *dataBuffer = self.bytes; for (int i = 0; i < self.length; ++i) { - [stringBuffer appendFormat:@"%02x", (UInt32)dataBuffer[i]]; + [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]]; } return stringBuffer; diff --git a/LivioHTTPServer/LivioHTTPServer/Core/LHSConnection.m b/LivioHTTPServer/LivioHTTPServer/Core/LHSConnection.m index 63636191..f7aa3ca5 100644 --- a/LivioHTTPServer/LivioHTTPServer/Core/LHSConnection.m +++ b/LivioHTTPServer/LivioHTTPServer/Core/LHSConnection.m @@ -1,5 +1,5 @@ -#import "GCDAsyncSocket.h" +#import #import "LHSServer.h" #import "LHSConnection.h" #import "LHSConnectionConfig.h" diff --git a/LivioHTTPServer/LivioHTTPServer/Core/LHSServer.m b/LivioHTTPServer/LivioHTTPServer/Core/LHSServer.m index 0a1530a3..f236b1d6 100644 --- a/LivioHTTPServer/LivioHTTPServer/Core/LHSServer.m +++ b/LivioHTTPServer/LivioHTTPServer/Core/LHSServer.m @@ -1,7 +1,7 @@ #import "LHSConnectionConfig.h" #import "LHSServer.h" -#import "GCDAsyncSocket.h" +#import #import "LHSConnection.h" #import "LHSWebSocket.h" diff --git a/LivioHTTPServer/LivioHTTPServer/Core/LHSWebSocket.m b/LivioHTTPServer/LivioHTTPServer/Core/LHSWebSocket.m index c5c6ce68..187852cc 100644 --- a/LivioHTTPServer/LivioHTTPServer/Core/LHSWebSocket.m +++ b/LivioHTTPServer/LivioHTTPServer/Core/LHSWebSocket.m @@ -1,7 +1,7 @@ #import "LHSWebSocket.h" #import "LHSMessage.h" -#import "GCDAsyncSocket.h" +#import #import "NSNumber+LHSNumber.h" #import "NSData+LHSData.h" diff --git a/LivioHTTPServer/LivioHTTPServer/LivioHTTPServer.h b/LivioHTTPServer/LivioHTTPServer/LivioHTTPServer.h index 6b9101c0..fb5f1fbe 100644 --- a/LivioHTTPServer/LivioHTTPServer/LivioHTTPServer.h +++ b/LivioHTTPServer/LivioHTTPServer/LivioHTTPServer.h @@ -68,6 +68,3 @@ FOUNDATION_EXPORT const unsigned char LivioHTTPServerVersionString[]; #import #import #import - -// GCDAsyncSocket -#import diff --git a/README.md b/README.md index c242bdca..2d8a62aa 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,21 @@ Sometimes developers need an embedded HTTP server in their app. Perhaps it's a s - Heavily commented code - Very easily extensible - WebDAV is supported too! + +## Getting Started + +If you’re using [Carthage](https://github.com/Carthage/Carthage), simply add LivioHTTPServer to your `Cartfile`: + +``` +github "livio/LivioHTTPServer" +``` +Make sure to add `CocoaAsyncSocket.framework` to "Linked Frameworks and Libraries" and "copy-frameworks" Build Phases. [More Information on Carthage](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos) + +To manually add LivioHTTPServer to your application: + +1. Add the LivioHTTPServer repository as a submodule of your application's repository. +2. Run `git submodule sync --quiet && git submodule update --init` from within the LivioHTTPServer folder. +3. Drag and drop `LivioHTTPServer.xcodeproj`, `Carthage/Checkouts/CocoaAsyncSocket/CocoaAsyncSocket.xcodeproj` into the top-level of your application's project file or workspace. +4. On the "Build Phases" tab of your application target, add `LivioHTTPServer.framework`, and `CocoaAsyncSocket.framework` to the "Link Binary With Libraries" phase. +5. Add `$(OBJROOT)/UninstalledProducts/include` and `$(inherited)` to the "Header Search Paths" build setting (this is only necessary for archive builds, but it has no negative effect otherwise). +6. **For iOS targets**, add `-ObjC` to the "Other Linker Flags" build setting.