[Pkg-owncloud-commits] [owncloud-client] 172/219: OS X: Update GCDAsyncSocket
Sandro Knauß
hefee-guest at moszumanska.debian.org
Sat Oct 11 14:43:23 UTC 2014
This is an automated email from the git hooks/post-receive script.
hefee-guest pushed a commit to branch master
in repository owncloud-client.
commit 0539098371e3010826217dec80c35ab57a9cadeb
Author: Markus Goetz <markus at woboq.com>
Date: Tue Sep 30 07:09:39 2014 +0200
OS X: Update GCDAsyncSocket
The two files are from the fork from
https://github.com/jdiehl/CocoaAsyncSocket/tree/socketUN
It supports unix domain sockets.
SHA1 was 7e97fc876522c7bf2a2a434cf40579ae9daf9da5
---
.../MacOSX/OwnCloudFinder/GCDAsyncSocket.h | 297 +-
.../MacOSX/OwnCloudFinder/GCDAsyncSocket.m | 3157 +++++++++++++-------
2 files changed, 2284 insertions(+), 1170 deletions(-)
diff --git a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h
index e53350b..0d62bc2 100644
--- a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h
+++ b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.h
@@ -3,25 +3,77 @@
//
// This class is in the public domain.
// Originally created by Robbie Hanson in Q3 2010.
-// Updated and maintained by Deusty LLC and the Mac development community.
+// Updated and maintained by Deusty LLC and the Apple development community.
//
-// http://code.google.com/p/cocoaasyncsocket/
+// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>
+#import <Security/SecureTransport.h>
#import <dispatch/dispatch.h>
@class GCDAsyncReadPacket;
@class GCDAsyncWritePacket;
+ at class GCDAsyncSocketPreBuffer;
+
+#if TARGET_OS_IPHONE
+
+ // Compiling for iOS
+
+ #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0 supported
+
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 // iOS 5.0 supported and required
+
+ #define IS_SECURE_TRANSPORT_AVAILABLE YES
+ #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
+ #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
+
+ #else // iOS 5.0 supported but not required
+
+ #ifndef NSFoundationVersionNumber_iPhoneOS_5_0
+ #define NSFoundationVersionNumber_iPhoneOS_5_0 881.00
+ #endif
+
+ #define IS_SECURE_TRANSPORT_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_5_0)
+ #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
+ #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
+
+ #endif
+
+ #else // iOS 5.0 not supported
+
+ #define IS_SECURE_TRANSPORT_AVAILABLE NO
+ #define SECURE_TRANSPORT_MAYBE_AVAILABLE 0
+ #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
+
+ #endif
+
+#else
+
+ // Compiling for Mac OS X
+
+ #define IS_SECURE_TRANSPORT_AVAILABLE YES
+ #define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
+ #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
+
+#endif
extern NSString *const GCDAsyncSocketException;
extern NSString *const GCDAsyncSocketErrorDomain;
-#if !TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketQueueName;
+extern NSString *const GCDAsyncSocketThreadName;
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
extern NSString *const GCDAsyncSocketSSLCipherSuites;
+#if TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
+#else
extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
#endif
+#endif
enum GCDAsyncSocketError
{
@@ -42,51 +94,6 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncSocket : NSObject
-{
- uint32_t flags;
- uint16_t config;
-
- id delegate;
- dispatch_queue_t delegateQueue;
-
- int socket4FD;
- int socket6FD;
- int connectIndex;
- NSData * connectInterface4;
- NSData * connectInterface6;
-
- dispatch_queue_t socketQueue;
-
- dispatch_source_t accept4Source;
- dispatch_source_t accept6Source;
- dispatch_source_t connectTimer;
- dispatch_source_t readSource;
- dispatch_source_t writeSource;
- dispatch_source_t readTimer;
- dispatch_source_t writeTimer;
-
- NSMutableArray *readQueue;
- NSMutableArray *writeQueue;
-
- GCDAsyncReadPacket *currentRead;
- GCDAsyncWritePacket *currentWrite;
-
- unsigned long socketFDBytesAvailable;
-
- NSMutableData *partialReadBuffer;
-
-#if TARGET_OS_IPHONE
- CFStreamClientContext streamContext;
- CFReadStreamRef readStream;
- CFWriteStreamRef writeStream;
-#else
- SSLContextRef sslContext;
- NSMutableData *sslReadBuffer;
- size_t sslWriteCachedLength;
-#endif
-
- id userData;
-}
/**
* GCDAsyncSocket uses the standard delegate paradigm,
@@ -99,6 +106,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* 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.
**/
@@ -122,39 +131,6 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
/**
- * Traditionally sockets are not closed until the conversation is over.
- * However, it is technically possible for the remote enpoint to close its write stream.
- * Our socket would then be notified that there is no more data to be read,
- * but our socket would still be writeable and the remote endpoint could continue to receive our data.
- *
- * The argument for this confusing functionality stems from the idea that a client could shut down its
- * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
- * In practice, however, this technique did little to help server developers.
- *
- * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
- * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
- * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
- * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
- *
- * In addition to the technical challenges and confusion, many high level socket/stream API's provide
- * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
- * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
- * It might sound like poor design at first, but in fact it simplifies development.
- *
- * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
- * Thus it actually makes sense to close the socket at this point.
- * And in fact this is what most networking developers want and expect to happen.
- * However, if you are writing a server that interacts with a plethora of clients,
- * you might encounter a client that uses the discouraged technique of shutting down its write stream.
- * If this is the case, you can set this property to NO,
- * and make use of the socketDidCloseReadStream delegate method.
- *
- * The default value is YES.
-**/
-- (BOOL)autoDisconnectOnClosedReadStream;
-- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
-
-/**
* By default, both IPv4 and IPv6 are enabled.
*
* For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
@@ -211,6 +187,15 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/
- (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
/**
@@ -326,6 +311,10 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
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
@@ -389,6 +378,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/
- (NSString *)connectedHost;
- (uint16_t)connectedPort;
+- (NSURL *)connectedUrl;
- (NSString *)localHost;
- (uint16_t)localPort;
@@ -646,6 +636,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
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
/**
@@ -667,6 +663,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/
- (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
/**
@@ -678,7 +680,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* Any reads or writes scheduled after this method is called will occur over the secured connection.
*
* The possible keys and values for the TLS settings are well documented.
- * Some possible keys are:
+ * Standard keys are:
+ *
* - kCFStreamSSLLevel
* - kCFStreamSSLAllowsExpiredCertificates
* - kCFStreamSSLAllowsExpiredRoots
@@ -688,6 +691,18 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* - kCFStreamSSLCertificates
* - kCFStreamSSLIsServer
*
+ * If SecureTransport is available on iOS:
+ *
+ * - GCDAsyncSocketSSLCipherSuites
+ * - GCDAsyncSocketSSLProtocolVersionMin
+ * - GCDAsyncSocketSSLProtocolVersionMax
+ *
+ * If SecureTransport is available on Mac OS X:
+ *
+ * - GCDAsyncSocketSSLCipherSuites
+ * - GCDAsyncSocketSSLDiffieHellmanParameters;
+ *
+ *
* Please refer to Apple's documentation for associated values, as well as other possible keys.
*
* If you pass in nil or an empty dictionary, the default settings will be used.
@@ -713,6 +728,114 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
#pragma mark Advanced
/**
+ * Traditionally sockets are not closed until the conversation is over.
+ * However, it is technically possible for the remote enpoint to close its write stream.
+ * Our socket would then be notified that there is no more data to be read,
+ * but our socket would still be writeable and the remote endpoint could continue to receive our data.
+ *
+ * The argument for this confusing functionality stems from the idea that a client could shut down its
+ * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
+ * In practice, however, this technique did little to help server developers.
+ *
+ * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
+ * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
+ * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
+ * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
+ *
+ * In addition to the technical challenges and confusion, many high level socket/stream API's provide
+ * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
+ * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
+ * It might sound like poor design at first, but in fact it simplifies development.
+ *
+ * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
+ * Thus it actually makes sense to close the socket at this point.
+ * And in fact this is what most networking developers want and expect to happen.
+ * However, if you are writing a server that interacts with a plethora of clients,
+ * you might encounter a client that uses the discouraged technique of shutting down its write stream.
+ * If this is the case, you can set this property to NO,
+ * and make use of the socketDidCloseReadStream delegate method.
+ *
+ * The default value is YES.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream;
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
+
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ * __block BOOL result = NO;
+ * dispatch_block_t block = ^{
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ * }
+ * if (is_executing_on_queue(socketQueue))
+ * block();
+ * else
+ * dispatch_sync(socketQueue, block);
+ *
+ * return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ * dispatch_set_target_queue(socketQueue, ipQueue);
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ * return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ * [clientConnections addObject:newSocket];
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
* It's not thread-safe to access certain variables from outside the socket's internal queue.
*
* For example, the socket file descriptor.
@@ -805,7 +928,9 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/
- (BOOL)enableBackgroundingOnSocket;
-#else
+#endif
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
/**
* This method is only available from within the context of a performBlock: invocation.
@@ -882,6 +1007,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+ **/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
+
+/**
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
diff --git a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m
index ca2474d..16a016f 100644
--- a/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m
+++ b/shell_integration/MacOSX/OwnCloudFinder/GCDAsyncSocket.m
@@ -3,15 +3,15 @@
//
// This class is in the public domain.
// Originally created by Robbie Hanson in Q4 2010.
-// Updated and maintained by Deusty LLC and the Mac development community.
+// Updated and maintained by Deusty LLC and the Apple development community.
//
-// http://code.google.com/p/cocoaasyncsocket/
+// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import "GCDAsyncSocket.h"
#if TARGET_OS_IPHONE
- #import <CFNetwork/CFNetwork.h>
+#import <CFNetwork/CFNetwork.h>
#endif
#import <arpa/inet.h>
@@ -25,15 +25,47 @@
#import <sys/ioctl.h>
#import <sys/poll.h>
#import <sys/uio.h>
+#import <sys/un.h>
#import <unistd.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
+
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
+**/
+#if TARGET_OS_IPHONE
+
+ // Compiling for iOS
+
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else // iOS 5.X or earlier
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+ #endif
+
+#else
+
+ // Compiling for Mac OS X
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
+ #endif
+
+#endif
+
#if 0
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
-// http://code.google.com/p/cocoalumberjack/
+// https://github.com/robbiehanson/CocoaLumberjack
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
@@ -97,10 +129,18 @@ static const int logLevel = LOG_LEVEL_VERBOSE;
NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
-#if !TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
+NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+#if TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
+NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
+#else
NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
#endif
+#endif
enum GCDAsyncSocketFlags
{
@@ -121,8 +161,9 @@ enum GCDAsyncSocketFlags
kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket
kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained
#if TARGET_OS_IPHONE
- kAddedStreamListener = 1 << 16, // If set, CFStreams have been added to listener thread
- kSecureSocketHasBytesAvailable = 1 << 17, // If set, CFReadStream has notified us of bytes available
+ kAddedStreamsToRunLoop = 1 << 16, // If set, CFStreams have been added to listener thread
+ kUsingCFStreamForTLS = 1 << 17, // If set, we're forced to use CFStream instead of SecureTransport
+ kSecureSocketHasBytesAvailable = 1 << 18, // If set, CFReadStream has notified us of bytes available
#endif
};
@@ -135,11 +176,67 @@ enum GCDAsyncSocketConfig
};
#if TARGET_OS_IPHONE
- static NSThread *listenerThread; // Used for CFStreams
+ static NSThread *cfstreamThread; // Used for CFStreams
#endif
- at interface GCDAsyncSocket (Private)
-
+ at interface GCDAsyncSocket ()
+{
+ uint32_t flags;
+ uint16_t config;
+
+#if __has_feature(objc_arc_weak)
+ __weak id delegate;
+#else
+ __unsafe_unretained id delegate;
+#endif
+ dispatch_queue_t delegateQueue;
+
+ int socket4FD;
+ int socket6FD;
+ int socketUN;
+ int connectIndex;
+ NSData * connectInterface4;
+ NSData * connectInterface6;
+ NSData * connectInterfaceUN;
+ NSURL * socketUrl;
+
+ 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
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+ SSLContextRef sslContext;
+ GCDAsyncSocketPreBuffer *sslPreBuffer;
+ size_t sslWriteCachedLength;
+ OSStatus sslErrCode;
+#endif
+
+ void *IsOnSocketQueueOrTargetQueueKey;
+
+ id userData;
+}
// Accepting
- (BOOL)doAccept:(int)socketFD;
@@ -151,12 +248,12 @@ enum GCDAsyncSocketConfig
- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
- (void)lookup:(int)aConnectIndex didFail:(NSError *)error;
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr;
+- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr;
- (void)didConnect:(int)aConnectIndex;
- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error;
// Disconnect
- (void)closeWithError:(NSError *)error;
-- (void)close;
- (void)maybeClose;
// Errors
@@ -181,6 +278,7 @@ enum GCDAsyncSocketConfig
- (NSString *)connectedHostFromSocket6:(int)socketFD;
- (uint16_t)connectedPortFromSocket4:(int)socketFD;
- (uint16_t)connectedPortFromSocket6:(int)socketFD;
+- (NSURL *)connectedUrlFromSocketUN:(int)socketFD;
- (NSString *)localHostFromSocket4:(int)socketFD;
- (NSString *)localHostFromSocket6:(int)socketFD;
- (uint16_t)localPortFromSocket4:(int)socketFD;
@@ -191,6 +289,7 @@ enum GCDAsyncSocketConfig
address6:(NSMutableData **)addr6Ptr
fromDescription:(NSString *)interfaceDescription
port:(uint16_t)port;
+- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url;
- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD;
- (void)suspendReadSource;
- (void)resumeReadSource;
@@ -219,13 +318,17 @@ enum GCDAsyncSocketConfig
// Security
- (void)maybeStartTLS;
-#if !TARGET_OS_IPHONE
-- (void)continueSSLHandshake;
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+- (void)ssl_startTLS;
+- (void)ssl_continueSSLHandshake;
+#endif
+#if TARGET_OS_IPHONE
+- (void)cf_startTLS;
#endif
// CFStream
#if TARGET_OS_IPHONE
-+ (void)startListenerThreadIfNeeded;
++ (void)startCFStreamThreadIfNeeded;
- (BOOL)createReadAndWriteStream;
- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite;
- (BOOL)addStreamsToRunLoop;
@@ -238,6 +341,159 @@ enum GCDAsyncSocketConfig
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr;
+
+ at end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A PreBuffer is used when there is more data available on the socket
+ * than is being requested by current read request.
+ * In this case we slurp up all data from the socket (to minimize sys calls),
+ * and store additional yet unread data in a "prebuffer".
+ *
+ * The prebuffer is entirely drained before we read from the socket again.
+ * In other words, a large chunk of data is written is written to the prebuffer.
+ * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
+ *
+ * A ring buffer was once used for this purpose.
+ * But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
+ * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
+ * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed.
+ *
+ * The current design is very simple and straight-forward, while also keeping memory requirements lower.
+**/
+
+ at 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;
+
+ at end
+
+ at implementation GCDAsyncSocketPreBuffer
+
+- (id)initWithCapacity:(size_t)numBytes
+{
+ if ((self = [super init]))
+ {
+ preBufferSize = numBytes;
+ preBuffer = malloc(preBufferSize);
+
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (preBuffer)
+ free(preBuffer);
+}
+
+- (void)ensureCapacityForWrite:(size_t)numBytes
+{
+ size_t availableSpace = preBufferSize - (writePointer - readPointer);
+
+ if (numBytes > availableSpace)
+ {
+ size_t additionalBytes = numBytes - availableSpace;
+
+ size_t newPreBufferSize = preBufferSize + additionalBytes;
+ uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
+
+ size_t readPointerOffset = readPointer - preBuffer;
+ size_t writePointerOffset = writePointer - preBuffer;
+
+ preBuffer = newPreBuffer;
+ preBufferSize = newPreBufferSize;
+
+ readPointer = preBuffer + readPointerOffset;
+ writePointer = preBuffer + writePointerOffset;
+ }
+}
+
+- (size_t)availableBytes
+{
+ return writePointer - readPointer;
+}
+
+- (uint8_t *)readBuffer
+{
+ return readPointer;
+}
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
+{
+ if (bufferPtr) *bufferPtr = readPointer;
+ if (availableBytesPtr) *availableBytesPtr = writePointer - readPointer;
+}
+
+- (void)didRead:(size_t)bytesRead
+{
+ readPointer += bytesRead;
+
+ if (readPointer == writePointer)
+ {
+ // The prebuffer has been drained. Reset pointers.
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+}
+
+- (size_t)availableSpace
+{
+ return preBufferSize - (writePointer - readPointer);
+}
+
+- (uint8_t *)writeBuffer
+{
+ return writePointer;
+}
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
+{
+ if (bufferPtr) *bufferPtr = writePointer;
+ if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer);
+}
+
+- (void)didWrite:(size_t)bytesWritten
+{
+ writePointer += bytesWritten;
+}
+
+- (void)reset
+{
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+}
@end
@@ -280,7 +536,7 @@ enum GCDAsyncSocketConfig
- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
-- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr;
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;
- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
@@ -307,7 +563,7 @@ enum GCDAsyncSocketConfig
if (d)
{
- buffer = [d retain];
+ buffer = d;
startOffset = s;
bufferOwner = NO;
originalBufferLength = [d length];
@@ -544,13 +800,13 @@ enum GCDAsyncSocketConfig
*
* It is assumed the terminator has not already been read.
**/
-- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
- NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!");
+ 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.
+ // 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:
@@ -587,7 +843,7 @@ enum GCDAsyncSocketConfig
BOOL found = NO;
NSUInteger termLength = [term length];
- NSUInteger preBufferLength = [preBuffer length];
+ NSUInteger preBufferLength = [preBuffer availableBytes];
if ((bytesDone + preBufferLength) < termLength)
{
@@ -612,11 +868,11 @@ enum GCDAsyncSocketConfig
uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
NSUInteger preLen = termLength - bufLen;
- const uint8_t *pre = [preBuffer bytes];
+ const uint8_t *pre = [preBuffer readBuffer];
NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
- NSUInteger result = preBufferLength;
+ NSUInteger result = maxPreBufferLength;
NSUInteger i;
for (i = 0; i < loopCount; i++)
@@ -645,7 +901,7 @@ enum GCDAsyncSocketConfig
if (memcmp(pre, termBuf, termLength) == 0)
{
- NSUInteger preOffset = pre - (const uint8_t *)[preBuffer bytes]; // pointer arithmetic
+ NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic
result = preOffset + termLength;
found = YES;
@@ -708,12 +964,6 @@ enum GCDAsyncSocketConfig
return -1;
}
-- (void)dealloc
-{
- [buffer release];
- [term release];
- [super dealloc];
-}
@end
@@ -741,7 +991,7 @@ enum GCDAsyncSocketConfig
{
if((self = [super init]))
{
- buffer = [d retain]; // Retain not copy. For performance as documented in header file.
+ buffer = d; // Retain not copy. For performance as documented in header file.
bytesDone = 0;
timeout = t;
tag = i;
@@ -749,11 +999,6 @@ enum GCDAsyncSocketConfig
return self;
}
-- (void)dealloc
-{
- [buffer release];
- [super dealloc];
-}
@end
@@ -784,11 +1029,6 @@ enum GCDAsyncSocketConfig
return self;
}
-- (void)dealloc
-{
- [tlsSettings release];
- [super dealloc];
-}
@end
@@ -818,15 +1058,16 @@ enum GCDAsyncSocketConfig
if((self = [super init]))
{
delegate = aDelegate;
+ delegateQueue = dq;
- if (dq)
- {
- dispatch_retain(dq);
- delegateQueue = dq;
- }
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (dq) dispatch_retain(dq);
+ #endif
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
+ socketUrl = nil;
connectIndex = 0;
if (sq)
@@ -838,21 +1079,45 @@ enum GCDAsyncSocketConfig
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
- dispatch_retain(sq);
socketQueue = sq;
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ dispatch_retain(sq);
+ #endif
}
else
{
- socketQueue = dispatch_queue_create("GCDAsyncSocket", NULL);
+ 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;
- partialReadBuffer = [[NSMutableData alloc] init];
+ preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
}
return self;
}
@@ -861,7 +1126,7 @@ enum GCDAsyncSocketConfig
{
LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
[self closeWithError:nil];
}
@@ -873,27 +1138,18 @@ enum GCDAsyncSocketConfig
}
delegate = nil;
- if (delegateQueue)
- dispatch_release(delegateQueue);
+
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue) dispatch_release(delegateQueue);
+ #endif
delegateQueue = NULL;
- dispatch_release(socketQueue);
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (socketQueue) dispatch_release(socketQueue);
+ #endif
socketQueue = NULL;
- [readQueue release];
- [writeQueue release];
-
- [partialReadBuffer release];
-
-#if !TARGET_OS_IPHONE
- [sslReadBuffer release];
-#endif
-
- [userData release];
-
LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
-
- [super dealloc];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -902,7 +1158,7 @@ enum GCDAsyncSocketConfig
- (id)delegate
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegate;
}
@@ -924,7 +1180,7 @@ enum GCDAsyncSocketConfig
delegate = newDelegate;
};
- if (dispatch_get_current_queue() == socketQueue) {
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
@@ -947,7 +1203,7 @@ enum GCDAsyncSocketConfig
- (dispatch_queue_t)delegateQueue
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegateQueue;
}
@@ -967,16 +1223,15 @@ enum GCDAsyncSocketConfig
{
dispatch_block_t block = ^{
- if (delegateQueue)
- dispatch_release(delegateQueue);
-
- if (newDelegateQueue)
- dispatch_retain(newDelegateQueue);
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue) dispatch_release(delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
delegateQueue = newDelegateQueue;
};
- if (dispatch_get_current_queue() == socketQueue) {
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
@@ -999,7 +1254,7 @@ enum GCDAsyncSocketConfig
- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (delegatePtr) *delegatePtr = delegate;
if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
@@ -1025,16 +1280,15 @@ enum GCDAsyncSocketConfig
delegate = newDelegate;
- if (delegateQueue)
- dispatch_release(delegateQueue);
-
- if (newDelegateQueue)
- dispatch_retain(newDelegateQueue);
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue) dispatch_release(delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
delegateQueue = newDelegateQueue;
};
- if (dispatch_get_current_queue() == socketQueue) {
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
@@ -1055,49 +1309,11 @@ enum GCDAsyncSocketConfig
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}
-- (BOOL)autoDisconnectOnClosedReadStream
-{
- // Note: YES means kAllowHalfDuplexConnection is OFF
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- return ((config & kAllowHalfDuplexConnection) == 0);
- }
- else
- {
- __block BOOL result;
-
- dispatch_sync(socketQueue, ^{
- result = ((config & kAllowHalfDuplexConnection) == 0);
- });
-
- return result;
- }
-}
-
-- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
-{
- // Note: YES means kAllowHalfDuplexConnection is OFF
-
- dispatch_block_t block = ^{
-
- if (flag)
- config &= ~kAllowHalfDuplexConnection;
- else
- config |= kAllowHalfDuplexConnection;
- };
-
- if (dispatch_get_current_queue() == socketQueue)
- block();
- else
- dispatch_async(socketQueue, block);
-}
-
- (BOOL)isIPv4Enabled
{
// Note: YES means kIPv4Disabled is OFF
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kIPv4Disabled) == 0);
}
@@ -1125,7 +1341,7 @@ enum GCDAsyncSocketConfig
config |= kIPv4Disabled;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
@@ -1135,7 +1351,7 @@ enum GCDAsyncSocketConfig
{
// Note: YES means kIPv6Disabled is OFF
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kIPv6Disabled) == 0);
}
@@ -1163,7 +1379,7 @@ enum GCDAsyncSocketConfig
config |= kIPv6Disabled;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
@@ -1173,7 +1389,7 @@ enum GCDAsyncSocketConfig
{
// Note: YES means kPreferIPv6 is OFF
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kPreferIPv6) == 0);
}
@@ -1201,7 +1417,7 @@ enum GCDAsyncSocketConfig
config |= kPreferIPv6;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
@@ -1213,15 +1429,15 @@ enum GCDAsyncSocketConfig
dispatch_block_t block = ^{
- result = [userData retain];
+ result = userData;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
- return [result autorelease];
+ return result;
}
- (void)setUserData:(id)arbitraryUserData
@@ -1230,12 +1446,11 @@ enum GCDAsyncSocketConfig
if (userData != arbitraryUserData)
{
- [userData release];
- userData = [arbitraryUserData retain];
+ userData = arbitraryUserData;
}
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
@@ -1255,7 +1470,7 @@ enum GCDAsyncSocketConfig
LogTrace();
// Just in-case interface parameter is immutable.
- NSString *interface = [[inInterface copy] autorelease];
+ NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *err = nil;
@@ -1270,7 +1485,7 @@ enum GCDAsyncSocketConfig
if (socketFD == SOCKET_NULL)
{
NSString *reason = @"Error in socket() function";
- err = [[self errnoErrorWithReason:reason] retain];
+ err = [self errnoErrorWithReason:reason];
return SOCKET_NULL;
}
@@ -1283,8 +1498,9 @@ enum GCDAsyncSocketConfig
if (status == -1)
{
NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
- err = [[self errnoErrorWithReason:reason] retain];
+ err = [self errnoErrorWithReason:reason];
+ LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
@@ -1294,20 +1510,23 @@ enum GCDAsyncSocketConfig
if (status == -1)
{
NSString *reason = @"Error enabling address reuse (setsockopt)";
- err = [[self errnoErrorWithReason:reason] retain];
+ 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]);
+ const struct sockaddr *addr = (const struct sockaddr *)[interfaceAddr bytes];
+ status = bind(socketFD, addr, addr->sa_len);
if (status == -1)
{
NSString *reason = @"Error in bind() function";
- err = [[self errnoErrorWithReason:reason] retain];
+ err = [self errnoErrorWithReason:reason];
+ LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
@@ -1318,8 +1537,9 @@ enum GCDAsyncSocketConfig
if (status == -1)
{
NSString *reason = @"Error in listen() function";
- err = [[self errnoErrorWithReason:reason] retain];
+ err = [self errnoErrorWithReason:reason];
+ LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
@@ -1329,24 +1549,21 @@ enum GCDAsyncSocketConfig
// Create dispatch block and run on socketQueue
- dispatch_block_t block = ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ 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] retain];
+ err = [self badConfigError:msg];
- [pool drain];
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] retain];
+ err = [self badConfigError:msg];
- [pool drain];
return_from_block;
}
@@ -1356,18 +1573,16 @@ enum GCDAsyncSocketConfig
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] retain];
+ err = [self badConfigError:msg];
- [pool drain];
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] retain];
+ err = [self badConfigError:msg];
- [pool drain];
return_from_block;
}
@@ -1385,27 +1600,24 @@ enum GCDAsyncSocketConfig
if ((interface4 == nil) && (interface6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
if (isIPv4Disabled && (interface6 == nil))
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
if (isIPv6Disabled && (interface4 == nil))
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
@@ -1421,7 +1633,6 @@ enum GCDAsyncSocketConfig
if (socket4FD == SOCKET_NULL)
{
- [pool drain];
return_from_block;
}
}
@@ -1445,10 +1656,10 @@ enum GCDAsyncSocketConfig
{
if (socket4FD != SOCKET_NULL)
{
+ LogVerbose(@"close(socket4FD)");
close(socket4FD);
}
- [pool drain];
return_from_block;
}
}
@@ -1462,8 +1673,7 @@ enum GCDAsyncSocketConfig
int socketFD = socket4FD;
dispatch_source_t acceptSource = accept4Source;
- dispatch_source_set_event_handler(accept4Source, ^{
- NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
LogVerbose(@"event4Block");
@@ -1473,14 +1683,14 @@ enum GCDAsyncSocketConfig
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([self doAccept:socketFD] && (++i < numPendingConnections));
-
- [eventPool drain];
- });
+ }});
dispatch_source_set_cancel_handler(accept4Source, ^{
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
LogVerbose(@"dispatch_release(accept4Source)");
dispatch_release(acceptSource);
+ #endif
LogVerbose(@"close(socket4FD)");
close(socketFD);
@@ -1497,8 +1707,7 @@ enum GCDAsyncSocketConfig
int socketFD = socket6FD;
dispatch_source_t acceptSource = accept6Source;
- dispatch_source_set_event_handler(accept6Source, ^{
- NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool {
LogVerbose(@"event6Block");
@@ -1508,14 +1717,14 @@ enum GCDAsyncSocketConfig
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([self doAccept:socketFD] && (++i < numPendingConnections));
-
- [eventPool drain];
- });
+ }});
dispatch_source_set_cancel_handler(accept6Source, ^{
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
LogVerbose(@"dispatch_release(accept6Source)");
dispatch_release(acceptSource);
+ #endif
LogVerbose(@"close(socket6FD)");
close(socketFD);
@@ -1528,10 +1737,9 @@ enum GCDAsyncSocketConfig
flags |= kSocketStarted;
result = YES;
- [pool drain];
- };
+ }};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
@@ -1541,55 +1749,271 @@ enum GCDAsyncSocketConfig
LogInfo(@"Error in accept: %@", err);
if (errPtr)
- *errPtr = [err autorelease];
- else
- [err release];
+ *errPtr = err;
}
return result;
}
-- (BOOL)doAccept:(int)parentSocketFD
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
{
LogTrace();
- BOOL isIPv4;
- int childSocketFD;
- NSData *childSocketAddress;
+ __block BOOL result = NO;
+ __block NSError *err = nil;
- if (parentSocketFD == socket4FD)
- {
- isIPv4 = YES;
-
- struct sockaddr_in addr;
- socklen_t addrLen = sizeof(addr);
+ // CreateSocket Block
+ // This block will be invoked within the dispatch block below.
+
+ int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
- childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+ int socketFD = socket(domain, SOCK_STREAM, 0);
- if (childSocketFD == -1)
+ if (socketFD == SOCKET_NULL)
{
- LogWarn(@"Accept failed with error: %@", [self errnoError]);
- return NO;
+ NSString *reason = @"Error in socket() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return SOCKET_NULL;
}
- childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
- }
- else // if (parentSocketFD == socket6FD)
- {
- isIPv4 = NO;
-
- struct sockaddr_in6 addr;
- socklen_t addrLen = sizeof(addr);
+ int status;
- childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+ // Set socket options
- if (childSocketFD == -1)
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
{
- LogWarn(@"Accept failed with error: %@", [self errnoError]);
- return NO;
+ NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
}
- childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ 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_current_queue() == socketQueue)
+ 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
@@ -1610,10 +2034,9 @@ enum GCDAsyncSocketConfig
if (delegateQueue)
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
// Query delegate for custom socket queue
@@ -1627,26 +2050,25 @@ enum GCDAsyncSocketConfig
// Create GCDAsyncSocket instance for accepted socket
- GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:delegate
+ GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate
delegateQueue:delegateQueue
socketQueue:childSocketQueue];
- if (isIPv4)
+ if (socketType == 0)
acceptedSocket->socket4FD = childSocketFD;
- else
+ else if (socketType == 1)
acceptedSocket->socket6FD = childSocketFD;
+ else
+ acceptedSocket->socketUN = childSocketFD;
acceptedSocket->flags = (kSocketStarted | kConnected);
// Setup read and write sources for accepted socket
- dispatch_async(acceptedSocket->socketQueue, ^{
- NSAutoreleasePool *socketPool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool {
[acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
-
- [socketPool drain];
- });
+ }});
// Notify delegate
@@ -1656,14 +2078,13 @@ enum GCDAsyncSocketConfig
}
// Release the socket queue returned from the delegate (it was retained by acceptedSocket)
- if (childSocketQueue)
- dispatch_release(childSocketQueue);
-
- // Release the accepted socket (it should have been retained by the delegate)
- [acceptedSocket release];
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (childSocketQueue) dispatch_release(childSocketQueue);
+ #endif
- [delegatePool drain];
- });
+ // The accepted socket should have been retained by the delegate.
+ // Otherwise it gets properly released when exiting the block.
+ }});
}
return YES;
@@ -1680,7 +2101,7 @@ enum GCDAsyncSocketConfig
**/
- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
{
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (delegate == nil) // Must have delegate set
{
@@ -1762,10 +2183,65 @@ enum GCDAsyncSocketConfig
return NO;
}
- connectInterface4 = [interface4 retain];
- connectInterface6 = [interface6 retain];
+ 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_current_queue() == socketQueue, @"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];
@@ -1795,23 +2271,21 @@ enum GCDAsyncSocketConfig
LogTrace();
// Just in case immutable objects were passed
- NSString *host = [[inHost copy] autorelease];
- NSString *interface = [[inInterface copy] autorelease];
+ NSString *host = [inHost copy];
+ NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *err = nil;
- dispatch_block_t block = ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
@@ -1819,8 +2293,6 @@ enum GCDAsyncSocketConfig
if (![self preConnectWithInterface:interface error:&err])
{
- [err retain];
- [pool drain];
return_from_block;
}
@@ -1836,24 +2308,20 @@ enum GCDAsyncSocketConfig
// This way the asynchronous lookup block below doesn't have to worry about it changing.
int aConnectIndex = connectIndex;
- NSString *hostCpy = [[host copy] autorelease];
+ NSString *hostCpy = [host copy];
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_async(globalConcurrentQueue, ^{
- NSAutoreleasePool *lookupPool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
[self lookup:aConnectIndex host:hostCpy port:port];
-
- [lookupPool drain];
- });
+ }});
[self startConnectTimeout:timeout];
result = YES;
- [pool drain];
- };
+ }};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
@@ -1861,9 +2329,7 @@ enum GCDAsyncSocketConfig
if (result == NO)
{
if (errPtr)
- *errPtr = [err autorelease];
- else
- [err release];
+ *errPtr = err;
}
return result;
@@ -1887,14 +2353,13 @@ enum GCDAsyncSocketConfig
LogTrace();
// Just in case immutable objects were passed
- NSData *remoteAddr = [[inRemoteAddr copy] autorelease];
- NSString *interface = [[inInterface copy] autorelease];
+ NSData *remoteAddr = [inRemoteAddr copy];
+ NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *err = nil;
- dispatch_block_t block = ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with remoteAddr parameter
@@ -1924,9 +2389,8 @@ enum GCDAsyncSocketConfig
if ((address4 == nil) && (address6 == nil))
{
NSString *msg = @"A valid IPv4 or IPv6 address was not given";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
@@ -1936,18 +2400,16 @@ enum GCDAsyncSocketConfig
if (isIPv4Disabled && (address4 != nil))
{
NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
if (isIPv6Disabled && (address6 != nil))
{
NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
- err = [[self badParamError:msg] retain];
+ err = [self badParamError:msg];
- [pool drain];
return_from_block;
}
@@ -1955,8 +2417,6 @@ enum GCDAsyncSocketConfig
if (![self preConnectWithInterface:interface error:&err])
{
- [err retain];
- [pool drain];
return_from_block;
}
@@ -1965,8 +2425,6 @@ enum GCDAsyncSocketConfig
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
- [err retain];
- [pool drain];
return_from_block;
}
@@ -1975,8 +2433,67 @@ enum GCDAsyncSocketConfig
[self startConnectTimeout:timeout];
result = YES;
- [pool drain];
- };
+ }};
+
+ 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 *err = nil;
+ if (![self connectWithAddressUN:connectInterfaceUN error:&err])
+ {
+ [self closeWithError:err];
+
+ return_from_block;
+ }
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
if (dispatch_get_current_queue() == socketQueue)
block();
@@ -1986,9 +2503,7 @@ enum GCDAsyncSocketConfig
if (result == NO)
{
if (errPtr)
- *errPtr = [err autorelease];
- else
- [err release];
+ *errPtr = err;
}
return result;
@@ -2075,19 +2590,17 @@ enum GCDAsyncSocketConfig
if (error)
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
[self lookup:aConnectIndex didFail:error];
- [pool drain];
- });
+ }});
}
else
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
[self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6];
- [pool drain];
- });
+ }});
}
}
@@ -2095,7 +2608,7 @@ enum GCDAsyncSocketConfig
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(address4 || address6, @"Expected at least one valid address");
if (aConnectIndex != connectIndex)
@@ -2149,7 +2662,7 @@ enum GCDAsyncSocketConfig
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aConnectIndex != connectIndex)
@@ -2169,7 +2682,7 @@ enum GCDAsyncSocketConfig
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ 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]);
@@ -2242,6 +2755,11 @@ enum GCDAsyncSocketConfig
}
}
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
// Start the connection process in a background queue
int aConnectIndex = connectIndex;
@@ -2252,21 +2770,19 @@ enum GCDAsyncSocketConfig
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
if (result == 0)
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
[self didConnect:aConnectIndex];
- [pool drain];
- });
+ }});
}
else
{
NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
[self didNotConnect:aConnectIndex error:error];
- [pool drain];
- });
+ }});
}
});
@@ -2275,12 +2791,93 @@ enum GCDAsyncSocketConfig
return YES;
}
-- (void)didConnect:(int)aConnectIndex
+- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"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 aConnectIndex = connectIndex;
+
+ 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:aConnectIndex];
+ }});
+ }
+ else
+ {
+ // TODO: Bad file descriptor
+ perror("connect");
+ NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self didNotConnect:aConnectIndex error:error];
+ }});
+ }
+ });
+
+ LogVerbose(@"Connecting...");
+
+ return YES;
+}
+
+- (void)didConnect:(int)aConnectIndex
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
if (aConnectIndex != connectIndex)
{
@@ -2354,28 +2951,39 @@ enum GCDAsyncSocketConfig
NSString *host = [self connectedHost];
uint16_t port = [self connectedPort];
+ NSURL *url = [self connectedUrl];
- if (delegateQueue && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+ if (delegateQueue && host != nil && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
{
SetupStreamsPart1();
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToHost:host port:port];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
-
- [callbackPool drain];
- });
+ }});
+ }});
+ }
+ else if (delegateQueue && url != nil && [delegate respondsToSelector:@selector(socket:didConnectToUrl:)])
+ {
+ SetupStreamsPart1();
+
+ __strong id theDelegate = delegate;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
- [delegatePool drain];
- });
+ [theDelegate socket:self didConnectToUrl:url];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ SetupStreamsPart2();
+ }});
+ }});
}
else
{
@@ -2385,7 +2993,7 @@ enum GCDAsyncSocketConfig
// Get the connected socket
- int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD;
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
// Enable non-blocking IO on the socket
@@ -2398,11 +3006,6 @@ enum GCDAsyncSocketConfig
return;
}
- // Prevent SIGPIPE signals
-
- int nosigpipe = 1;
- setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
-
// Setup our read/write sources
[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
@@ -2417,7 +3020,7 @@ enum GCDAsyncSocketConfig
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aConnectIndex != connectIndex)
@@ -2429,7 +3032,6 @@ enum GCDAsyncSocketConfig
return;
}
- [self endConnectTimeout];
[self closeWithError:error];
}
@@ -2439,19 +3041,18 @@ enum GCDAsyncSocketConfig
{
connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
- dispatch_source_set_event_handler(connectTimer, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
[self doConnectTimeout];
-
- [pool drain];
- });
+ }});
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_source_t theConnectTimer = connectTimer;
dispatch_source_set_cancel_handler(connectTimer, ^{
LogVerbose(@"dispatch_release(connectTimer)");
dispatch_release(theConnectTimer);
});
+ #endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
@@ -2480,12 +3081,10 @@ enum GCDAsyncSocketConfig
if (connectInterface4)
{
- [connectInterface4 release];
connectInterface4 = nil;
}
if (connectInterface6)
{
- [connectInterface6 release];
connectInterface6 = nil;
}
}
@@ -2506,7 +3105,7 @@ enum GCDAsyncSocketConfig
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
[self endConnectTimeout];
@@ -2517,7 +3116,7 @@ enum GCDAsyncSocketConfig
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
- [partialReadBuffer setLength:0];
+ [preBuffer reset];
#if TARGET_OS_IPHONE
{
@@ -2541,15 +3140,25 @@ enum GCDAsyncSocketConfig
}
}
}
- #else
+ #endif
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
{
- [sslReadBuffer setLength:0];
+ [sslPreBuffer reset];
+ sslErrCode = noErr;
+
if (sslContext)
{
- // Getting a linker error here about SSLDisposeContext?
+ // Getting a linker error here about the SSLx() functions?
// You need to add the Security Framework to your application.
+ SSLClose(sslContext);
+
+ #if TARGET_OS_IPHONE
+ CFRelease(sslContext);
+ #else
SSLDisposeContext(sslContext);
+ #endif
+
sslContext = NULL;
}
}
@@ -2560,62 +3169,92 @@ enum GCDAsyncSocketConfig
// 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)
- {
- LogVerbose(@"dispatch_source_cancel(accept4Source)");
- dispatch_source_cancel(accept4Source);
-
- // We never suspend accept4Source
-
- accept4Source = NULL;
- }
-
- if (accept6Source)
+ if (!accept4Source && !accept6Source && !readSource && !writeSource)
{
- LogVerbose(@"dispatch_source_cancel(accept6Source)");
- dispatch_source_cancel(accept6Source);
-
- // We never suspend accept6Source
-
- accept6Source = NULL;
- }
- if (!readSource && !writeSource) {
LogVerbose(@"manually closing close");
- if (socket4FD) {
+ if (socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket4FD)");
close(socket4FD);
+ socket4FD = SOCKET_NULL;
}
- if (socket6FD) {
+ 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;
}
}
-
- if (readSource)
+ else
{
- LogVerbose(@"dispatch_source_cancel(readSource)");
- dispatch_source_cancel(readSource);
+ if (accept4Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept4Source)");
+ dispatch_source_cancel(accept4Source);
+
+ // We never suspend accept4Source
+
+ accept4Source = NULL;
+ }
- [self resumeReadSource];
+ if (accept6Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept6Source)");
+ dispatch_source_cancel(accept6Source);
+
+ // We never suspend accept6Source
+
+ accept6Source = NULL;
+ }
- readSource = NULL;
- }
+ if (acceptUNSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
+ dispatch_source_cancel(acceptUNSource);
+
+ // We never suspend acceptUNSource
+
+ acceptUNSource = NULL;
+ }
- if (writeSource)
- {
- LogVerbose(@"dispatch_source_cancel(writeSource)");
- dispatch_source_cancel(writeSource);
+ if (readSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(readSource)");
+ dispatch_source_cancel(readSource);
+
+ [self resumeReadSource];
+
+ readSource = NULL;
+ }
- [self resumeWriteSource];
+ if (writeSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(writeSource)");
+ dispatch_source_cancel(writeSource);
+
+ [self resumeWriteSource];
+
+ writeSource = NULL;
+ }
- writeSource = NULL;
+ // The sockets will be closed by the cancel handlers of the corresponding source
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
}
- // The sockets will be closed by the cancel handlers of the corresponding source
-
- socket4FD = SOCKET_NULL;
- socket6FD = SOCKET_NULL;
-
// If the client has passed the connect/accept method, then the connection has at least begun.
// Notify delegate that it is now ending.
BOOL shouldCallDelegate = (flags & kSocketStarted);
@@ -2628,35 +3267,29 @@ enum GCDAsyncSocketConfig
{
if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidDisconnect:self withError:error];
-
- [pool drain];
- });
+ }});
}
}
}
- (void)disconnect
{
- dispatch_block_t block = ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_block_t block = ^{ @autoreleasepool {
if (flags & kSocketStarted)
{
[self closeWithError:nil];
}
-
- [pool drain];
- };
+ }};
// Synchronous disconnection, as documented in the header file
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
@@ -2664,47 +3297,38 @@ enum GCDAsyncSocketConfig
- (void)disconnectAfterReading
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterReads);
[self maybeClose];
}
-
- [pool drain];
- });
+ }});
}
- (void)disconnectAfterWriting
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
[self maybeClose];
}
-
- [pool drain];
- });
+ }});
}
- (void)disconnectAfterReadingAndWriting
{
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
[self maybeClose];
}
-
- [pool drain];
- });
+ }});
}
/**
@@ -2714,7 +3338,7 @@ enum GCDAsyncSocketConfig
**/
- (void)maybeClose
{
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
BOOL shouldClose = NO;
@@ -2883,7 +3507,7 @@ enum GCDAsyncSocketConfig
result = (flags & kSocketStarted) ? NO : YES;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
@@ -2899,7 +3523,7 @@ enum GCDAsyncSocketConfig
result = (flags & kConnected) ? YES : NO;
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
@@ -2909,7 +3533,7 @@ enum GCDAsyncSocketConfig
- (NSString *)connectedHost
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self connectedHostFromSocket4:socket4FD];
@@ -2922,24 +3546,21 @@ enum GCDAsyncSocketConfig
{
__block NSString *result = nil;
- dispatch_sync(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
if (socket4FD != SOCKET_NULL)
- result = [[self connectedHostFromSocket4:socket4FD] retain];
+ result = [self connectedHostFromSocket4:socket4FD];
else if (socket6FD != SOCKET_NULL)
- result = [[self connectedHostFromSocket6:socket6FD] retain];
-
- [pool drain];
- });
+ result = [self connectedHostFromSocket6:socket6FD];
+ }});
- return [result autorelease];
+ return result;
}
}
- (uint16_t)connectedPort
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self connectedPortFromSocket4:socket4FD];
@@ -2965,10 +3586,33 @@ enum GCDAsyncSocketConfig
}
}
-- (NSString *)localHost
+- (NSURL *)connectedUrl
{
if (dispatch_get_current_queue() == socketQueue)
{
+ 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)
@@ -2980,24 +3624,21 @@ enum GCDAsyncSocketConfig
{
__block NSString *result = nil;
- dispatch_sync(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
if (socket4FD != SOCKET_NULL)
- result = [[self localHostFromSocket4:socket4FD] retain];
+ result = [self localHostFromSocket4:socket4FD];
else if (socket6FD != SOCKET_NULL)
- result = [[self localHostFromSocket6:socket6FD] retain];
-
- [pool drain];
- });
+ result = [self localHostFromSocket6:socket6FD];
+ }});
- return [result autorelease];
+ return result;
}
}
- (uint16_t)localPort
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self localPortFromSocket4:socket4FD];
@@ -3135,6 +3776,18 @@ enum GCDAsyncSocketConfig
return [[self class] portFromSockaddr6:&sockaddr6];
}
+- (NSURL *)connectedUrlFromSocketUN:(int)socketFD
+{
+ struct sockaddr_un sockaddr;
+ socklen_t sockaddrlen = sizeof(sockaddr);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
+ {
+ return 0;
+ }
+ return [[self class] urlFromSockaddrUN:&sockaddr];
+}
+
- (NSString *)localHostFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
@@ -3211,12 +3864,12 @@ enum GCDAsyncSocketConfig
}
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
- return [result autorelease];
+ return result;
}
- (NSData *)localAddress
@@ -3247,17 +3900,17 @@ enum GCDAsyncSocketConfig
}
};
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
- return [result autorelease];
+ return result;
}
- (BOOL)isIPv4
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (socket4FD != SOCKET_NULL);
}
@@ -3275,7 +3928,7 @@ enum GCDAsyncSocketConfig
- (BOOL)isIPv6
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (socket6FD != SOCKET_NULL);
}
@@ -3293,7 +3946,7 @@ enum GCDAsyncSocketConfig
- (BOOL)isSecure
{
- if (dispatch_get_current_queue() == socketQueue)
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (flags & kSocketSecure) ? YES : NO;
}
@@ -3483,6 +4136,22 @@ enum GCDAsyncSocketConfig
if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}
+- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url;
+{
+ NSString *path = url.path;
+ if (path.length == 0) {
+ return nil;
+ }
+
+ struct sockaddr_un nativeAddr;
+ nativeAddr.sun_family = AF_UNIX;
+ strcpy(nativeAddr.sun_path, path.fileSystemRepresentation);
+ 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);
@@ -3490,8 +4159,7 @@ enum GCDAsyncSocketConfig
// Setup event handlers
- dispatch_source_set_event_handler(readSource, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
LogVerbose(@"readEventBlock");
@@ -3502,34 +4170,33 @@ enum GCDAsyncSocketConfig
[self doReadData];
else
[self doReadEOF];
-
- [pool drain];
- });
+ }});
- dispatch_source_set_event_handler(writeSource, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
LogVerbose(@"writeEventBlock");
flags |= kSocketCanAcceptBytes;
[self doWriteData];
-
- [pool drain];
- });
+ }});
// Setup cancel handlers
__block int socketFDRefCount = 2;
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_source_t theReadSource = readSource;
dispatch_source_t theWriteSource = writeSource;
+ #endif
dispatch_source_set_cancel_handler(readSource, ^{
LogVerbose(@"readCancelBlock");
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
LogVerbose(@"dispatch_release(readSource)");
dispatch_release(theReadSource);
+ #endif
if (--socketFDRefCount == 0)
{
@@ -3542,8 +4209,10 @@ enum GCDAsyncSocketConfig
LogVerbose(@"writeCancelBlock");
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
LogVerbose(@"dispatch_release(writeSource)");
dispatch_release(theWriteSource);
+ #endif
if (--socketFDRefCount == 0)
{
@@ -3565,11 +4234,11 @@ enum GCDAsyncSocketConfig
flags |= kWriteSourceSuspended;
}
-- (BOOL)usingCFStream
+- (BOOL)usingCFStreamForTLS
{
#if TARGET_OS_IPHONE
-
- if (flags & kSocketSecure)
+ {
+ if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
{
// Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS,
// we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo!
@@ -3578,12 +4247,23 @@ enum GCDAsyncSocketConfig
return YES;
}
-
+ }
#endif
return NO;
}
+- (BOOL)usingSecureTransportForTLS
+{
+ #if TARGET_OS_IPHONE
+ {
+ return ![self usingCFStreamForTLS];
+ }
+ #endif
+
+ return YES;
+}
+
- (void)suspendReadSource
{
if (!(flags & kReadSourceSuspended))
@@ -3664,8 +4344,7 @@ enum GCDAsyncSocketConfig
terminator:nil
tag:tag];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
@@ -3674,13 +4353,10 @@ enum GCDAsyncSocketConfig
[readQueue addObject:packet];
[self maybeDequeueRead];
}
-
- [pool drain];
- });
+ }});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
- [packet release];
}
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
@@ -3711,8 +4387,7 @@ enum GCDAsyncSocketConfig
terminator:nil
tag:tag];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
@@ -3721,13 +4396,10 @@ enum GCDAsyncSocketConfig
[readQueue addObject:packet];
[self maybeDequeueRead];
}
-
- [pool drain];
- });
+ }});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
- [packet release];
}
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
@@ -3777,8 +4449,7 @@ enum GCDAsyncSocketConfig
terminator:data
tag:tag];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
@@ -3787,13 +4458,54 @@ enum GCDAsyncSocketConfig
[readQueue addObject:packet];
[self maybeDequeueRead];
}
-
- [pool drain];
- });
+ }});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
- [packet release];
+}
+
+- (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;
}
/**
@@ -3809,7 +4521,7 @@ enum GCDAsyncSocketConfig
- (void)maybeDequeueRead
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ 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))
@@ -3817,7 +4529,7 @@ enum GCDAsyncSocketConfig
if ([readQueue count] > 0)
{
// Dequeue the next object in the write queue
- currentRead = [[readQueue objectAtIndex:0] retain];
+ currentRead = [readQueue objectAtIndex:0];
[readQueue removeObjectAtIndex:0];
@@ -3870,9 +4582,9 @@ enum GCDAsyncSocketConfig
//
// Be sure callbacks are enabled so we're notified about a disconnection.
- if ([partialReadBuffer length] == 0)
+ if ([preBuffer availableBytes] == 0)
{
- if ([self usingCFStream]) {
+ if ([self usingCFStreamForTLS]) {
// Callbacks never disabled
}
else {
@@ -3889,43 +4601,44 @@ enum GCDAsyncSocketConfig
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
- if ([partialReadBuffer length] > 0)
+ if ([preBuffer availableBytes] > 0)
{
- // Only flush the ssl buffers if the partialReadBuffer is empty.
- // This is to avoid growing the partialReadBuffer inifinitely large.
+ // 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 ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ if ([self usingCFStreamForTLS])
{
- LogVerbose(@"%@ - Flushing ssl buffers into partialReadBuffer...", THIS_METHOD);
-
- CFIndex defaultBytesToRead = (1024 * 16);
-
- NSUInteger partialReadBufferOffset = [partialReadBuffer length];
- [partialReadBuffer increaseLengthBy:defaultBytesToRead];
-
- uint8_t *buffer = [partialReadBuffer mutableBytes] + partialReadBufferOffset;
-
- CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
- LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
-
- if (result <= 0)
- {
- [partialReadBuffer setLength:partialReadBufferOffset];
- }
- else
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
{
- [partialReadBuffer setLength:(partialReadBufferOffset + result)];
+ 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;
}
- flags &= ~kSecureSocketHasBytesAvailable;
+ return;
}
-#else
+#endif
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
__block NSUInteger estimatedBytesAvailable = 0;
@@ -3933,15 +4646,15 @@ enum GCDAsyncSocketConfig
// Figure out if there is any data available to be read
//
- // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
- // [sslReadBuffer length] <- Number of encrypted bytes we've buffered from bsd socket
- // sslInternalBufSize <- Number od decrypted bytes SecureTransport has buffered
+ // 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 sslReadBuffer.
+ // from the encrypted bytes in the sslPreBuffer.
// However, we do know this is an upper bound on the estimation.
- estimatedBytesAvailable = socketFDBytesAvailable + [sslReadBuffer length];
+ estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
@@ -3953,28 +4666,31 @@ enum GCDAsyncSocketConfig
if (estimatedBytesAvailable > 0)
{
- LogVerbose(@"%@ - Flushing ssl buffers into partialReadBuffer...", THIS_METHOD);
+ LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
BOOL done = NO;
do
{
LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
- // Make room in the partialReadBuffer
+ // Make sure there's enough room in the prebuffer
- NSUInteger partialReadBufferOffset = [partialReadBuffer length];
- [partialReadBuffer increaseLengthBy:estimatedBytesAvailable];
+ [preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
- uint8_t *buffer = (uint8_t *)[partialReadBuffer mutableBytes] + partialReadBufferOffset;
- size_t bytesRead = 0;
+ // Read data into prebuffer
- // Read data into partialReadBuffer
+ 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);
- [partialReadBuffer setLength:(partialReadBufferOffset + bytesRead)];
- LogVerbose(@"%@ - partialReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[partialReadBuffer length]);
+ if (bytesRead > 0)
+ {
+ [preBuffer didWrite:bytesRead];
+ }
+
+ LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
if (result != noErr)
{
@@ -4023,7 +4739,7 @@ enum GCDAsyncSocketConfig
[self flushSSLBuffers];
}
- if ([self usingCFStream])
+ if ([self usingCFStreamForTLS])
{
// CFReadStream only fires once when there is available data.
// It won't fire again until we've invoked CFReadStreamRead.
@@ -4045,29 +4761,26 @@ enum GCDAsyncSocketConfig
}
BOOL hasBytesAvailable;
- unsigned long estimatedBytesAvailable;
+ unsigned long estimatedBytesAvailable = 0;
- #if TARGET_OS_IPHONE
+ if ([self usingCFStreamForTLS])
{
- if (flags & kSocketSecure)
- {
- // Relegated to using CFStream... :( Boo! Give us SecureTransport Apple!
-
- estimatedBytesAvailable = 0;
- if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
- hasBytesAvailable = YES;
- else
- hasBytesAvailable = NO;
- }
+ #if TARGET_OS_IPHONE
+
+ // Relegated to using CFStream... :( Boo! Give us a full SecureTransport stack Apple!
+
+ estimatedBytesAvailable = 0;
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ hasBytesAvailable = YES;
else
- {
- estimatedBytesAvailable = socketFDBytesAvailable;
- hasBytesAvailable = (estimatedBytesAvailable > 0);
-
- }
+ hasBytesAvailable = NO;
+
+ #endif
}
- #else
- {
+ else
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
estimatedBytesAvailable = socketFDBytesAvailable;
if (flags & kSocketSecure)
@@ -4084,9 +4797,9 @@ enum GCDAsyncSocketConfig
// 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 sslReadBuffer.
+ // and may store excess in the sslPreBuffer.
- estimatedBytesAvailable += [sslReadBuffer length];
+ estimatedBytesAvailable += [sslPreBuffer availableBytes];
// The second buffer is within SecureTransport.
// As mentioned earlier, there are encrypted packets coming across the TCP stream.
@@ -4105,18 +4818,19 @@ enum GCDAsyncSocketConfig
estimatedBytesAvailable += sslInternalBufSize;
}
-
+
hasBytesAvailable = (estimatedBytesAvailable > 0);
+
+ #endif
}
- #endif
- if ((hasBytesAvailable == NO) && ([partialReadBuffer length] == 0))
+ if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
{
LogVerbose(@"No data available to read...");
// No data available to read.
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Need to wait for readSource to fire and notify us of
// available data in the socket's internal read buffer.
@@ -4134,21 +4848,24 @@ enum GCDAsyncSocketConfig
if (flags & kStartingWriteTLS)
{
- #if !TARGET_OS_IPHONE
+ if ([self usingSecureTransportForTLS])
{
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
// We are in the process of a SSL Handshake.
// We were waiting for incoming data which has just arrived.
- [self continueSSLHandshake];
+ [self ssl_continueSSLHandshake];
+
+ #endif
}
- #endif
}
else
{
// We are still waiting for the writeQueue to drain and start the SSL/TLS process.
// We now know data is available to read.
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Suspend the read source or else it will continue to fire nonstop.
@@ -4168,9 +4885,7 @@ enum GCDAsyncSocketConfig
// STEP 1 - READ FROM PREBUFFER
//
- NSUInteger partialReadBufferLength = [partialReadBuffer length];
-
- if (partialReadBufferLength > 0)
+ if ([preBuffer availableBytes] > 0)
{
// There are 3 types of read packets:
//
@@ -4184,13 +4899,13 @@ enum GCDAsyncSocketConfig
{
// Read type #3 - read up to a terminator
- bytesToCopy = [currentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done];
+ bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
}
else
{
// Read type #1 or #2
- bytesToCopy = [currentRead readLengthForNonTermWithHint:partialReadBufferLength];
+ bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
}
// Make sure we have enough room in the buffer for our read.
@@ -4202,13 +4917,12 @@ enum GCDAsyncSocketConfig
uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
currentRead->bytesDone;
- memcpy(buffer, [partialReadBuffer bytes], bytesToCopy);
+ memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
- // Remove the copied bytes from the partial read buffer
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
- partialReadBufferLength -= bytesToCopy;
+ // Remove the copied bytes from the preBuffer
+ [preBuffer didRead:bytesToCopy];
- LogVerbose(@"copied(%lu) partialReadBufferLength(%lu)", bytesToCopy, partialReadBufferLength);
+ LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
// Update totals
@@ -4262,7 +4976,7 @@ enum GCDAsyncSocketConfig
if (!done && !error && !socketEOF && !waiting && hasBytesAvailable)
{
- NSAssert((partialReadBufferLength == 0), @"Invalid logic");
+ NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
// There are 3 types of read packets:
//
@@ -4270,12 +4984,12 @@ enum GCDAsyncSocketConfig
// 2) Read a specific length of data.
// 3) Read up to a particular terminator.
- BOOL readIntoPartialReadBuffer = NO;
+ BOOL readIntoPreBuffer = NO;
NSUInteger bytesToRead;
- if ([self usingCFStream])
+ if ([self usingCFStreamForTLS])
{
- // Since Apple has neglected to make SecureTransport available on iOS,
+ // Since Apple hasn't made the full power of SecureTransport available on iOS,
// we are relegated to using the slower, less powerful, RunLoop based CFStream API.
//
// This API doesn't tell us how much data is available on the socket to be read.
@@ -4287,7 +5001,7 @@ enum GCDAsyncSocketConfig
NSUInteger defaultReadLength = (1024 * 32);
bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
- shouldPreBuffer:&readIntoPartialReadBuffer];
+ shouldPreBuffer:&readIntoPreBuffer];
}
else
{
@@ -4296,7 +5010,7 @@ enum GCDAsyncSocketConfig
// Read type #3 - read up to a terminator
bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
- shouldPreBuffer:&readIntoPartialReadBuffer];
+ shouldPreBuffer:&readIntoPreBuffer];
}
else
{
@@ -4314,18 +5028,15 @@ enum GCDAsyncSocketConfig
// 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 partialReadBuffer.
+ // or we're reading into the temporary preBuffer.
uint8_t *buffer;
- if (readIntoPartialReadBuffer)
+ if (readIntoPreBuffer)
{
- if (bytesToRead > partialReadBufferLength)
- {
- [partialReadBuffer setLength:bytesToRead];
- }
-
- buffer = [partialReadBuffer mutableBytes];
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
}
else
{
@@ -4340,24 +5051,20 @@ enum GCDAsyncSocketConfig
if (flags & kSocketSecure)
{
- #if TARGET_OS_IPHONE
- {
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
if (result < 0)
{
- error = [NSMakeCollectable(CFReadStreamCopyError(readStream)) autorelease];
-
- if (readIntoPartialReadBuffer)
- [partialReadBuffer setLength:0];
+ error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
}
else if (result == 0)
{
socketEOF = YES;
-
- if (readIntoPartialReadBuffer)
- [partialReadBuffer setLength:0];
}
else
{
@@ -4369,9 +5076,13 @@ enum GCDAsyncSocketConfig
// 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;
- }
- #else
- {
+
+ #endif
+ }
+ else
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
// The documentation from Apple states:
//
// "a read operation might return errSSLWouldBlock,
@@ -4400,29 +5111,38 @@ enum GCDAsyncSocketConfig
if (result == errSSLWouldBlock)
waiting = YES;
else
- error = [self sslError:result];
-
- // It's possible that bytesRead > 0, yet the result is errSSLWouldBlock.
+ {
+ 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;
-
- if (readIntoPartialReadBuffer)
- [partialReadBuffer setLength:0];
}
}
// Do not modify socketFDBytesAvailable.
// It will be updated via the SSLReadFunction().
- }
- #endif
+
+ #endif
+ }
}
else
{
- int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+ 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);
@@ -4435,26 +5155,30 @@ enum GCDAsyncSocketConfig
error = [self errnoErrorWithReason:@"Error in read() function"];
socketFDBytesAvailable = 0;
-
- if (readIntoPartialReadBuffer)
- [partialReadBuffer setLength:0];
}
else if (result == 0)
{
socketEOF = YES;
socketFDBytesAvailable = 0;
-
- if (readIntoPartialReadBuffer)
- [partialReadBuffer setLength:0];
}
else
{
bytesRead = result;
- if (socketFDBytesAvailable <= bytesRead)
+ 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
- socketFDBytesAvailable -= bytesRead;
+ {
+ if (socketFDBytesAvailable <= bytesRead)
+ socketFDBytesAvailable = 0;
+ else
+ socketFDBytesAvailable -= bytesRead;
+ }
if (socketFDBytesAvailable == 0)
{
@@ -4473,7 +5197,7 @@ enum GCDAsyncSocketConfig
//
// Note: We should never be using a prebuffer when we're reading a specific length of data.
- NSAssert(readIntoPartialReadBuffer == NO, @"Invalid logic");
+ NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
@@ -4484,18 +5208,17 @@ enum GCDAsyncSocketConfig
{
// Read type #3 - read up to a terminator
- if (readIntoPartialReadBuffer)
+ if (readIntoPreBuffer)
{
- // We just read a big chunk of data into the partialReadBuffer.
- // Search for the terminating sequence.
- //
- // Note: We are depending upon [partialReadBuffer length] to tell us how much data is
- // available in the partialReadBuffer. So we need to be sure this matches how many bytes
- // have actually been read into said buffer.
+ // We just read a big chunk of data into the preBuffer
+
+ [preBuffer didWrite:bytesRead];
+ LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
- [partialReadBuffer setLength:bytesRead];
+ // Search for the terminating sequence
- bytesToRead = [currentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done];
+ bytesToRead = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+ LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToRead);
// Ensure there's room on the read packet's buffer
@@ -4503,14 +5226,14 @@ enum GCDAsyncSocketConfig
// Copy bytes from prebuffer into read buffer
- uint8_t *preBuf = [partialReadBuffer mutableBytes];
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
- + currentRead->bytesDone;
+ + currentRead->bytesDone;
- memcpy(readBuf, preBuf, bytesToRead);
+ memcpy(readBuf, [preBuffer readBuffer], bytesToRead);
// Remove the copied bytes from the prebuffer
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0];
+ [preBuffer didRead:bytesToRead];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
// Update totals
currentRead->bytesDone += bytesToRead;
@@ -4543,10 +5266,16 @@ enum GCDAsyncSocketConfig
NSInteger underflow = bytesRead - overflow;
- // Copy excess data into partialReadBuffer
- void *overflowBuffer = buffer + currentRead->bytesDone + underflow;
+ // 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);
- [partialReadBuffer appendBytes:overflowBuffer length:overflow];
+ [preBuffer didWrite:overflow];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
// Note: The completeCurrentRead method will trim the buffer for us.
@@ -4579,34 +5308,30 @@ enum GCDAsyncSocketConfig
{
// Read type #1 - read all available data
- if (readIntoPartialReadBuffer)
+ if (readIntoPreBuffer)
{
- // We just read a chunk of data into the partialReadBuffer.
- // Copy the data into the read packet.
+ // 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.
//
- // Note: We are depending upon [partialReadBuffer length] to tell us how much data is
- // available in the partialReadBuffer. So we need to be sure this matches how many bytes
- // have actually been read into said buffer.
-
- [partialReadBuffer setLength:bytesRead];
-
// Ensure there's room on the read packet's buffer
[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
// Copy bytes from prebuffer into read buffer
- uint8_t *preBuf = [partialReadBuffer mutableBytes];
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
- + currentRead->bytesDone;
+ + currentRead->bytesDone;
- memcpy(readBuf, preBuf, bytesRead);
+ memcpy(readBuf, [preBuffer readBuffer], bytesRead);
// Remove the copied bytes from the prebuffer
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesRead) withBytes:NULL length:0];
+ [preBuffer didRead:bytesRead];
// Update totals
currentRead->bytesDone += bytesRead;
@@ -4641,7 +5366,7 @@ enum GCDAsyncSocketConfig
{
[self completeCurrentRead];
- if (!error && (!socketEOF || partialReadBufferLength > 0))
+ if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
{
[self maybeDequeueRead];
}
@@ -4652,16 +5377,13 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
{
- id theDelegate = delegate;
- GCDAsyncReadPacket *theRead = currentRead;
+ __strong id theDelegate = delegate;
+ long theReadTag = currentRead->tag;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
- [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theRead->tag];
-
- [pool drain];
- });
+ [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
+ }});
}
}
@@ -4677,7 +5399,7 @@ enum GCDAsyncSocketConfig
}
else if (waiting)
{
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Monitor the socket for readability (if we're not already doing so)
[self resumeReadSource];
@@ -4692,14 +5414,14 @@ enum GCDAsyncSocketConfig
LogTrace();
// This method may be called more than once.
- // If the EOF is read while there is still data in the partialReadBuffer,
+ // 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 partialReadBuffer now.
+ // If the SSL layer has any buffered data, flush it into the preBuffer now.
[self flushSSLBuffers];
}
@@ -4714,13 +5436,16 @@ enum GCDAsyncSocketConfig
shouldDisconnect = YES;
- #if !TARGET_OS_IPHONE
+ if ([self usingSecureTransportForTLS])
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
error = [self sslError:errSSLClosedAbort];
- #endif
+ #endif
+ }
}
else if (flags & kReadStreamClosed)
{
- // The partialReadBuffer has already been drained.
+ // 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.
@@ -4730,7 +5455,7 @@ enum GCDAsyncSocketConfig
shouldDisconnect = NO;
}
- else if ([partialReadBuffer length] > 0)
+ else if ([preBuffer availableBytes] > 0)
{
LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
@@ -4747,7 +5472,7 @@ enum GCDAsyncSocketConfig
//
// Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
- int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
struct pollfd pfd[1];
pfd[0].fd = socketFD;
@@ -4767,15 +5492,12 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidCloseReadStream:self];
-
- [pool drain];
- });
+ }});
}
}
else
@@ -4793,13 +5515,29 @@ enum GCDAsyncSocketConfig
{
if (error == nil)
{
- error = [self connectionClosedError];
+ if ([self usingSecureTransportForTLS])
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+ if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
+ {
+ error = [self sslError:sslErrCode];
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
+ #endif
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
}
[self closeWithError:error];
}
else
{
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Suspend the read source (if needed)
@@ -4848,16 +5586,13 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)])
{
- id theDelegate = delegate;
- GCDAsyncReadPacket *theRead = currentRead;
+ __strong id theDelegate = delegate;
+ GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didReadData:result withTag:theRead->tag];
-
- [pool drain];
- });
+ }});
}
[self endCurrentRead];
@@ -4871,7 +5606,6 @@ enum GCDAsyncSocketConfig
readTimer = NULL;
}
- [currentRead release];
currentRead = nil;
}
@@ -4881,17 +5615,18 @@ enum GCDAsyncSocketConfig
{
readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
- dispatch_source_set_event_handler(readTimer, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
+
[self doReadTimeout];
- [pool drain];
- });
+ }});
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_source_t theReadTimer = readTimer;
dispatch_source_set_cancel_handler(readTimer, ^{
LogVerbose(@"dispatch_release(readTimer)");
dispatch_release(theReadTimer);
});
+ #endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
@@ -4911,11 +5646,10 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
GCDAsyncReadPacket *theRead = currentRead;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
NSTimeInterval timeoutExtension = 0.0;
@@ -4923,16 +5657,11 @@ enum GCDAsyncSocketConfig
elapsed:theRead->timeout
bytesDone:theRead->bytesDone];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
[self doReadTimeoutWithExtension:timeoutExtension];
-
- [callbackPool drain];
- });
-
- [delegatePool drain];
- });
+ }});
+ }});
}
else
{
@@ -4975,8 +5704,7 @@ enum GCDAsyncSocketConfig
GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
@@ -4985,13 +5713,47 @@ enum GCDAsyncSocketConfig
[writeQueue addObject:packet];
[self maybeDequeueWrite];
}
-
- [pool drain];
- });
+ }});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
- [packet release];
+}
+
+- (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;
}
/**
@@ -5007,7 +5769,7 @@ enum GCDAsyncSocketConfig
- (void)maybeDequeueWrite
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ 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
@@ -5016,7 +5778,7 @@ enum GCDAsyncSocketConfig
if ([writeQueue count] > 0)
{
// Dequeue the next object in the write queue
- currentWrite = [[writeQueue objectAtIndex:0] retain];
+ currentWrite = [writeQueue objectAtIndex:0];
[writeQueue removeObjectAtIndex:0];
@@ -5070,7 +5832,7 @@ enum GCDAsyncSocketConfig
// Unable to write at this time
- if ([self usingCFStream])
+ if ([self usingCFStreamForTLS])
{
// CFWriteStream only fires once when there is available data.
// It won't fire again until we've invoked CFWriteStreamWrite.
@@ -5094,7 +5856,7 @@ enum GCDAsyncSocketConfig
// No space available to write.
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Need to wait for writeSource to fire and notify us of
// available space in the socket's internal write buffer.
@@ -5112,21 +5874,24 @@ enum GCDAsyncSocketConfig
if (flags & kStartingReadTLS)
{
- #if !TARGET_OS_IPHONE
+ if ([self usingSecureTransportForTLS])
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
// We are in the process of a SSL Handshake.
// We were waiting for available space in the socket's internal OS buffer to continue writing.
- [self continueSSLHandshake];
+ [self ssl_continueSSLHandshake];
- #endif
+ #endif
+ }
}
else
{
// We are still waiting for the readQueue to drain and start the SSL/TLS process.
// We now know we can write to the socket.
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
// Suspend the write source or else it will continue to fire nonstop.
@@ -5137,7 +5902,7 @@ enum GCDAsyncSocketConfig
return;
}
- // Note: This method is not called if theCurrentWrite is an GCDAsyncSpecialPacket (startTLS packet)
+ // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
BOOL waiting = NO;
NSError *error = nil;
@@ -5145,7 +5910,13 @@ enum GCDAsyncSocketConfig
if (flags & kSocketSecure)
{
- #if TARGET_OS_IPHONE
+ 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;
@@ -5157,11 +5928,11 @@ enum GCDAsyncSocketConfig
}
CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
- LogVerbose(@"CFWriteStreamWrite(%lu) = %li", bytesToWrite, result);
+ LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
if (result < 0)
{
- error = [NSMakeCollectable(CFWriteStreamCopyError(writeStream)) autorelease];
+ error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
}
else
{
@@ -5173,7 +5944,11 @@ enum GCDAsyncSocketConfig
waiting = YES;
}
- #else
+ #endif
+ }
+ else
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
// We're going to use the SSLWrite function.
//
@@ -5198,10 +5973,11 @@ enum GCDAsyncSocketConfig
//
// This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
// then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
- // but it sets bytesWritten to bytesToWrite !!
+ // 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.
+ // 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,
@@ -5232,7 +6008,7 @@ enum GCDAsyncSocketConfig
bytesWritten = sslWriteCachedLength;
sslWriteCachedLength = 0;
- if (currentWrite->bytesDone == [currentWrite->buffer length])
+ if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
{
// We've written all data for the current write.
hasNewDataToWrite = NO;
@@ -5256,7 +6032,9 @@ enum GCDAsyncSocketConfig
if (hasNewDataToWrite)
{
- const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone + bytesWritten;
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+ + currentWrite->bytesDone
+ + bytesWritten;
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
@@ -5302,11 +6080,16 @@ enum GCDAsyncSocketConfig
} // if (hasNewDataToWrite)
- #endif
+ #endif
+ }
}
else
{
- int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+ //
+ // 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;
@@ -5351,7 +6134,7 @@ enum GCDAsyncSocketConfig
{
flags &= ~kSocketCanAcceptBytes;
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
@@ -5365,7 +6148,7 @@ enum GCDAsyncSocketConfig
{
// Update total amount read for the current write
currentWrite->bytesDone += bytesWritten;
- LogVerbose(@"currentWrite->bytesDone = %lu", currentWrite->bytesDone);
+ LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone);
// Is packet done?
done = (currentWrite->bytesDone == [currentWrite->buffer length]);
@@ -5391,7 +6174,7 @@ enum GCDAsyncSocketConfig
flags &= ~kSocketCanAcceptBytes;
- if (![self usingCFStream])
+ if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
@@ -5403,16 +6186,13 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
{
- id theDelegate = delegate;
- GCDAsyncWritePacket *theWrite = currentWrite;
+ __strong id theDelegate = delegate;
+ long theWriteTag = currentWrite->tag;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
- [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWrite->tag];
-
- [pool drain];
- });
+ [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
+ }});
}
}
}
@@ -5436,16 +6216,13 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
{
- id theDelegate = delegate;
- GCDAsyncWritePacket *theWrite = currentWrite;
+ __strong id theDelegate = delegate;
+ long theWriteTag = currentWrite->tag;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
- [theDelegate socket:self didWriteDataWithTag:theWrite->tag];
-
- [pool drain];
- });
+ [theDelegate socket:self didWriteDataWithTag:theWriteTag];
+ }});
}
[self endCurrentWrite];
@@ -5459,7 +6236,6 @@ enum GCDAsyncSocketConfig
writeTimer = NULL;
}
- [currentWrite release];
currentWrite = nil;
}
@@ -5469,19 +6245,18 @@ enum GCDAsyncSocketConfig
{
writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
- dispatch_source_set_event_handler(writeTimer, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
[self doWriteTimeout];
-
- [pool drain];
- });
+ }});
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_source_t theWriteTimer = writeTimer;
dispatch_source_set_cancel_handler(writeTimer, ^{
LogVerbose(@"dispatch_release(writeTimer)");
dispatch_release(theWriteTimer);
});
+ #endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
@@ -5501,11 +6276,10 @@ enum GCDAsyncSocketConfig
if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
GCDAsyncWritePacket *theWrite = currentWrite;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
NSTimeInterval timeoutExtension = 0.0;
@@ -5513,16 +6287,11 @@ enum GCDAsyncSocketConfig
elapsed:theWrite->timeout
bytesDone:theWrite->bytesDone];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
[self doWriteTimeoutWithExtension:timeoutExtension];
-
- [callbackPool drain];
- });
-
- [delegatePool drain];
- });
+ }});
+ }});
}
else
{
@@ -5578,8 +6347,7 @@ enum GCDAsyncSocketConfig
GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
- dispatch_async(socketQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites))
{
@@ -5591,24 +6359,73 @@ enum GCDAsyncSocketConfig
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
-
- [pool drain];
- });
+ }});
+
+}
+
+- (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
- [packet release];
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ BOOL canUseSecureTransport = YES;
+
+ #if TARGET_OS_IPHONE
+ {
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+
+ NSNumber *value;
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
+ if (value && [value boolValue] == YES)
+ canUseSecureTransport = NO;
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
+ if (value && [value boolValue] == YES)
+ canUseSecureTransport = NO;
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+ if (value && [value boolValue] == NO)
+ canUseSecureTransport = NO;
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
+ if (value && [value boolValue] == YES)
+ canUseSecureTransport = NO;
+ }
+ #endif
+
+ if (IS_SECURE_TRANSPORT_AVAILABLE && canUseSecureTransport)
+ {
+ #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+ [self ssl_startTLS];
+ #endif
+ }
+ else
+ {
+ #if TARGET_OS_IPHONE
+ [self cf_startTLS];
+ #endif
+ }
+ }
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Security - Mac OS X
+#pragma mark Security via SecureTransport
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#if !TARGET_OS_IPHONE
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
{
LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
- if ((socketFDBytesAvailable == 0) && ([sslReadBuffer length] == 0))
+ if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0))
{
LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
@@ -5633,25 +6450,24 @@ enum GCDAsyncSocketConfig
// STEP 1 : READ FROM SSL PRE BUFFER
//
- NSUInteger sslReadBufferLength = [sslReadBuffer length];
+ size_t sslPreBufferLength = [sslPreBuffer availableBytes];
- if (sslReadBufferLength > 0)
+ if (sslPreBufferLength > 0)
{
LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
size_t bytesToCopy;
- if (sslReadBufferLength > totalBytesLeftToBeRead)
+ if (sslPreBufferLength > totalBytesLeftToBeRead)
bytesToCopy = totalBytesLeftToBeRead;
else
- bytesToCopy = (size_t)sslReadBufferLength;
-
- LogVerbose(@"%@: Copying %zu bytes from sslReadBuffer", THIS_METHOD, bytesToCopy);
+ bytesToCopy = sslPreBufferLength;
- memcpy(buffer, [sslReadBuffer mutableBytes], bytesToCopy);
+ LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy);
- [sslReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
+ memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
- LogVerbose(@"%@: sslReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[sslReadBuffer length]);
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
totalBytesRead += bytesToCopy;
totalBytesLeftToBeRead -= bytesToCopy;
@@ -5669,7 +6485,7 @@ enum GCDAsyncSocketConfig
{
LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
- int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
BOOL readIntoPreBuffer;
size_t bytesToRead;
@@ -5677,19 +6493,16 @@ enum GCDAsyncSocketConfig
if (socketFDBytesAvailable > totalBytesLeftToBeRead)
{
- // Read all available data from socket into sslReadBuffer.
+ // Read all available data from socket into sslPreBuffer.
// Then copy requested amount into dataBuffer.
- LogVerbose(@"%@: Reading into sslReadBuffer...", THIS_METHOD);
+ LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);
- if ([sslReadBuffer length] < socketFDBytesAvailable)
- {
- [sslReadBuffer setLength:socketFDBytesAvailable];
- }
+ [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];
readIntoPreBuffer = YES;
bytesToRead = (size_t)socketFDBytesAvailable;
- buf = [sslReadBuffer mutableBytes];
+ buf = [sslPreBuffer writeBuffer];
}
else
{
@@ -5715,11 +6528,6 @@ enum GCDAsyncSocketConfig
}
socketFDBytesAvailable = 0;
-
- if (readIntoPreBuffer)
- {
- [sslReadBuffer setLength:0];
- }
}
else if (result == 0)
{
@@ -5727,11 +6535,6 @@ enum GCDAsyncSocketConfig
socketError = YES;
socketFDBytesAvailable = 0;
-
- if (readIntoPreBuffer)
- {
- [sslReadBuffer setLength:0];
- }
}
else
{
@@ -5744,19 +6547,19 @@ enum GCDAsyncSocketConfig
if (readIntoPreBuffer)
{
- size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
+ [sslPreBuffer didWrite:bytesReadFromSocket];
- LogVerbose(@"%@: Copying %zu bytes out of sslReadBuffer", THIS_METHOD, bytesToCopy);
+ size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
- memcpy((uint8_t *)buffer + totalBytesRead, [sslReadBuffer bytes], bytesToCopy);
+ LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy);
- [sslReadBuffer setLength:bytesReadFromSocket];
- [sslReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
+ memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
totalBytesRead += bytesToCopy;
totalBytesLeftToBeRead -= bytesToCopy;
- LogVerbose(@"%@: sslReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[sslReadBuffer length]);
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
}
else
{
@@ -5802,7 +6605,7 @@ enum GCDAsyncSocketConfig
BOOL done = NO;
BOOL socketError = NO;
- int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
ssize_t result = write(socketFD, buffer, bytesToWrite);
@@ -5839,189 +6642,293 @@ enum GCDAsyncSocketConfig
static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
{
- GCDAsyncSocket *asyncSocket = (GCDAsyncSocket *)connection;
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
- NSCAssert(dispatch_get_current_queue() == asyncSocket->socketQueue, @"What the deuce?");
+ 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 = (GCDAsyncSocket *)connection;
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
- NSCAssert(dispatch_get_current_queue() == asyncSocket->socketQueue, @"What the deuce?");
+ NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
return [asyncSocket sslWriteWithBuffer:data length:dataLength];
}
-- (void)maybeStartTLS
+- (void)ssl_startTLS
{
LogTrace();
- // 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
+ LogVerbose(@"Starting TLS (via SecureTransport)...");
+
+ OSStatus status;
- if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+
+ // Create SSLContext, and setup IO callbacks and connection ref
+
+ BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue];
+
+ #if TARGET_OS_IPHONE
{
- LogVerbose(@"Starting TLS...");
+ if (isServer)
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
+ else
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
- OSStatus status;
+ if (sslContext == NULL)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLCreateContext"]];
+ return;
+ }
+ }
+ #else
+ {
+ status = SSLNewContext(isServer, &sslContext);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+ return;
+ }
+ }
+ #endif
+
+ status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+ return;
+ }
+
+ status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+ return;
+ }
+
+ // Configure SSLContext from given settings
+ //
+ // Checklist:
+ // 1. kCFStreamSSLPeerName
+ // 2. kCFStreamSSLAllowsAnyRoot
+ // 3. kCFStreamSSLAllowsExpiredRoots
+ // 4. kCFStreamSSLValidatesCertificateChain
+ // 5. kCFStreamSSLAllowsExpiredCertificates
+ // 6. kCFStreamSSLCertificates
+ // 7. kCFStreamSSLLevel (GCDAsyncSocketSSLProtocolVersionMin / GCDAsyncSocketSSLProtocolVersionMax)
+ // 8. GCDAsyncSocketSSLCipherSuites
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
+
+ id value;
+
+ // 1. kCFStreamSSLPeerName
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName];
+ if ([value isKindOfClass:[NSString class]])
+ {
+ NSString *peerName = (NSString *)value;
- GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
- NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+ const char *peer = [peerName UTF8String];
+ size_t peerLen = strlen(peer);
- // Create SSLContext, and setup IO callbacks and connection ref
+ status = SSLSetPeerDomainName(sslContext, peer, peerLen);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
+ return;
+ }
+ }
+
+ // 2. kCFStreamSSLAllowsAnyRoot
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
+ if (value)
+ {
+ #if TARGET_OS_IPHONE
+ NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsAnyRoot");
+ #else
- BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue];
+ BOOL allowsAnyRoot = [value boolValue];
- status = SSLNewContext(isServer, &sslContext);
+ status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot);
if (status != noErr)
{
- [self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+ [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]];
return;
}
- status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+ #endif
+ }
+
+ // 3. kCFStreamSSLAllowsExpiredRoots
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
+ if (value)
+ {
+ #if TARGET_OS_IPHONE
+ NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredRoots");
+ #else
+
+ BOOL allowsExpiredRoots = [value boolValue];
+
+ status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots);
if (status != noErr)
{
- [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+ [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]];
return;
}
- status = SSLSetConnection(sslContext, (SSLConnectionRef)self);
+ #endif
+ }
+
+ // 4. kCFStreamSSLValidatesCertificateChain
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+ if (value)
+ {
+ #if TARGET_OS_IPHONE
+ NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLValidatesCertificateChain");
+ #else
+
+ BOOL validatesCertChain = [value boolValue];
+
+ status = SSLSetEnableCertVerify(sslContext, validatesCertChain);
if (status != noErr)
{
- [self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+ [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
return;
}
- // Configure SSLContext from given settings
- //
- // Checklist:
- // 1. kCFStreamSSLPeerName
- // 2. kCFStreamSSLAllowsAnyRoot
- // 3. kCFStreamSSLAllowsExpiredRoots
- // 4. kCFStreamSSLValidatesCertificateChain
- // 5. kCFStreamSSLAllowsExpiredCertificates
- // 6. kCFStreamSSLCertificates
- // 7. kCFStreamSSLLevel
- // 8. GCDAsyncSocketSSLCipherSuites
- // 9. GCDAsyncSocketSSLDiffieHellmanParameters
-
- id value;
+ #endif
+ }
+
+ // 5. kCFStreamSSLAllowsExpiredCertificates
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
+ if (value)
+ {
+ #if TARGET_OS_IPHONE
+ NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredCertificates");
+ #else
- // 1. kCFStreamSSLPeerName
+ BOOL allowsExpiredCerts = [value boolValue];
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName];
- if ([value isKindOfClass:[NSString class]])
+ status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts);
+ if (status != noErr)
{
- 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;
- }
+ [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]];
+ return;
}
- // 2. kCFStreamSSLAllowsAnyRoot
+ #endif
+ }
+
+ // 6. kCFStreamSSLCertificates
+
+ value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates];
+ if (value)
+ {
+ CFArrayRef certs = (__bridge CFArrayRef)value;
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
- if (value)
+ status = SSLSetCertificate(sslContext, certs);
+ if (status != noErr)
{
- BOOL allowsAnyRoot = [value boolValue];
-
- status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot);
- if (status != noErr)
- {
- [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]];
- return;
- }
+ [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+ return;
}
+ }
+
+ // 7. kCFStreamSSLLevel
+
+ #if TARGET_OS_IPHONE
+ {
+ NSString *sslLevel = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel];
- // 3. kCFStreamSSLAllowsExpiredRoots
+ NSString *sslMinLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
+ NSString *sslMaxLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
- if (value)
+ if (sslLevel)
{
- BOOL allowsExpiredRoots = [value boolValue];
-
- status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots);
- if (status != noErr)
+ if (sslMinLevel || sslMaxLevel)
{
- [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]];
- return;
+ LogWarn(@"kCFStreamSSLLevel security option ignored. Overriden by "
+ @"GCDAsyncSocketSSLProtocolVersionMin and/or GCDAsyncSocketSSLProtocolVersionMax");
+ }
+ else
+ {
+ if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3])
+ {
+ sslMinLevel = sslMaxLevel = @"kSSLProtocol3";
+ }
+ else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1])
+ {
+ sslMinLevel = sslMaxLevel = @"kTLSProtocol1";
+ }
+ else
+ {
+ LogWarn(@"Unable to match kCFStreamSSLLevel security option to valid SSL protocol min/max");
+ }
}
}
- // 4. kCFStreamSSLValidatesCertificateChain
-
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
- if (value)
+ if (sslMinLevel || sslMaxLevel)
{
- BOOL validatesCertChain = [value boolValue];
+ OSStatus status1 = noErr;
+ OSStatus status2 = noErr;
+
+ SSLProtocol (^sslProtocolForString)(NSString*) = ^SSLProtocol (NSString *protocolStr) {
+
+ if ([protocolStr isEqualToString:@"kSSLProtocol3"]) return kSSLProtocol3;
+ if ([protocolStr isEqualToString:@"kTLSProtocol1"]) return kTLSProtocol1;
+ if ([protocolStr isEqualToString:@"kTLSProtocol11"]) return kTLSProtocol11;
+ if ([protocolStr isEqualToString:@"kTLSProtocol12"]) return kTLSProtocol12;
+
+ return kSSLProtocolUnknown;
+ };
- status = SSLSetEnableCertVerify(sslContext, validatesCertChain);
- if (status != noErr)
+ SSLProtocol minProtocol = sslProtocolForString(sslMinLevel);
+ SSLProtocol maxProtocol = sslProtocolForString(sslMaxLevel);
+
+ if (minProtocol != kSSLProtocolUnknown)
{
- [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
- return;
+ status1 = SSLSetProtocolVersionMin(sslContext, minProtocol);
}
- }
-
- // 5. kCFStreamSSLAllowsExpiredCertificates
-
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
- if (value)
- {
- BOOL allowsExpiredCerts = [value boolValue];
-
- status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts);
- if (status != noErr)
+ if (maxProtocol != kSSLProtocolUnknown)
{
- [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]];
- return;
+ status2 = SSLSetProtocolVersionMax(sslContext, maxProtocol);
}
- }
-
- // 6. kCFStreamSSLCertificates
-
- value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates];
- if (value)
- {
- CFArrayRef certs = (CFArrayRef)value;
- status = SSLSetCertificate(sslContext, certs);
- if (status != noErr)
+ if (status1 != noErr || status2 != noErr)
{
- [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMinMax"]];
return;
}
}
-
- // 7. kCFStreamSSLLevel
-
+ }
+ #else
+ {
value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel];
if (value)
{
NSString *sslLevel = (NSString *)value;
+ OSStatus status1 = noErr;
+ OSStatus status2 = noErr;
+ OSStatus status3 = noErr;
+
if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2])
{
// kCFStreamSocketSecurityLevelSSLv2:
//
// Specifies that SSL version 2 be set as the security protocol.
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES);
+ status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+ status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES);
}
else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3])
{
@@ -6030,9 +6937,9 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
// Specifies that SSL version 3 be set as the security protocol.
// If SSL version 3 is not available, specifies that SSL version 2 be set as the security protocol.
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES);
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3, YES);
+ status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+ status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2, YES);
+ status3 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3, YES);
}
else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1])
{
@@ -6040,8 +6947,8 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
//
// Specifies that TLS version 1 be set as the security protocol.
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
- SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1, YES);
+ status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+ status2 = SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1, YES);
}
else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL])
{
@@ -6049,70 +6956,85 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
//
// Specifies that the highest level security protocol that can be negotiated be used.
- SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES);
- }
- }
-
- // 8. GCDAsyncSocketSSLCipherSuites
-
- value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
- if (value)
- {
- NSArray *cipherSuites = (NSArray *)value;
- NSUInteger numberCiphers = [cipherSuites count];
- SSLCipherSuite ciphers[numberCiphers];
-
- NSUInteger cipherIndex;
- for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
- {
- NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
- ciphers[cipherIndex] = [cipherObject shortValue];
+ status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES);
}
- status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
- if (status != noErr)
+ if (status1 != noErr || status2 != noErr || status3 != noErr)
{
- [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionEnabled"]];
return;
}
}
+ }
+ #endif
+
+ // 8. GCDAsyncSocketSSLCipherSuites
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+ if (value)
+ {
+ NSArray *cipherSuites = (NSArray *)value;
+ NSUInteger numberCiphers = [cipherSuites count];
+ SSLCipherSuite ciphers[numberCiphers];
- // 9. GCDAsyncSocketSSLDiffieHellmanParameters
-
- value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
- if (value)
+ NSUInteger cipherIndex;
+ for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
{
- NSData *diffieHellmanData = (NSData *)value;
-
- status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
- if (status != noErr)
- {
- [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
- return;
- }
+ NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
+ ciphers[cipherIndex] = [cipherObject shortValue];
}
- // Setup the sslReadBuffer
- //
- // If there is any data in the partialReadBuffer,
- // this needs to be moved into the sslReadBuffer,
- // as this data is now part of the secure read stream.
-
- sslReadBuffer = [[NSMutableData alloc] init];
-
- if ([partialReadBuffer length] > 0)
+ status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+ if (status != noErr)
{
- [sslReadBuffer appendData:partialReadBuffer];
- [partialReadBuffer setLength:0];
+ [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+ return;
}
+ }
+
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters
+
+ #if !TARGET_OS_IPHONE
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+ if (value)
+ {
+ NSData *diffieHellmanData = (NSData *)value;
- // Start the SSL Handshake process
+ status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+ return;
+ }
+ }
+ #endif
+
+ // Setup the sslPreBuffer
+ //
+ // Any data in the preBuffer needs to be moved into the sslPreBuffer,
+ // as this data is now part of the secure read stream.
+
+ sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+
+ size_t preBufferLength = [preBuffer availableBytes];
+
+ if (preBufferLength > 0)
+ {
+ [sslPreBuffer ensureCapacityForWrite:preBufferLength];
- [self continueSSLHandshake];
+ memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
+ [preBuffer didRead:preBufferLength];
+ [sslPreBuffer didWrite:preBufferLength];
}
+
+ sslErrCode = noErr;
+
+ // Start the SSL Handshake process
+
+ [self ssl_continueSSLHandshake];
}
-- (void)continueSSLHandshake
+- (void)ssl_continueSSLHandshake
{
LogTrace();
@@ -6133,15 +7055,12 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidSecure:self];
-
- [pool drain];
- });
+ }});
}
[self endCurrentRead];
@@ -6167,12 +7086,12 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Security - iOS
+#pragma mark Security via CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
-- (void)finishSSLHandshake
+- (void)cf_finishSSLHandshake
{
LogTrace();
@@ -6185,15 +7104,12 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
{
- id theDelegate = delegate;
+ __strong id theDelegate = delegate;
- dispatch_async(delegateQueue, ^{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidSecure:self];
-
- [pool drain];
- });
+ }});
}
[self endCurrentRead];
@@ -6204,7 +7120,7 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
}
}
-- (void)abortSSLHandshake:(NSError *)error
+- (void)cf_abortSSLHandshake:(NSError *)error
{
LogTrace();
@@ -6217,98 +7133,91 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
}
}
-- (void)maybeStartTLS
+- (void)cf_startTLS
{
LogTrace();
- // 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
+ LogVerbose(@"Starting TLS (via CFStream)...");
- if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ if ([preBuffer availableBytes] > 0)
{
- LogVerbose(@"Starting TLS...");
-
- if ([partialReadBuffer length] > 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;
-
- 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;
- NSDictionary *tlsSettings = tlsPacket->tlsSettings;
-
- // Getting an error concerning kCFStreamPropertySSLSettings ?
- // You need to add the CFNetwork framework to your iOS application.
-
- BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFDictionaryRef)tlsSettings);
- BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFDictionaryRef)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;
- }
+ NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
- LogVerbose(@"Waiting for SSL Handshake to complete...");
+ [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
@@ -6319,43 +7228,41 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
#if TARGET_OS_IPHONE
-+ (void)startListenerThreadIfNeeded
++ (void)startCFStreamThreadIfNeeded
{
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
- listenerThread = [[NSThread alloc] initWithTarget:self
- selector:@selector(listenerThread)
+ cfstreamThread = [[NSThread alloc] initWithTarget:self
+ selector:@selector(cfstreamThread)
object:nil];
- [listenerThread start];
+ [cfstreamThread start];
});
}
-+ (void)listenerThread
++ (void)cfstreamThread { @autoreleasepool
{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ [[NSThread currentThread] setName:GCDAsyncSocketThreadName];
- LogInfo(@"ListenerThread: Started");
+ 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 a decades.
+ // 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:)
+ selector:@selector(doNothingAtAll:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
- LogInfo(@"ListenerThread: Stopped");
-
- [pool drain];
-}
+ LogInfo(@"CFStreamThread: Stopped");
+}}
-+ (void)addStreamListener:(GCDAsyncSocket *)asyncSocket
++ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
LogTrace();
- NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
@@ -6366,10 +7273,10 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
}
-+ (void)removeStreamListener:(GCDAsyncSocket *)asyncSocket
++ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
LogTrace();
- NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
@@ -6382,21 +7289,19 @@ static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data,
static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
- GCDAsyncSocket *asyncSocket = [(GCDAsyncSocket *)pInfo retain];
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
switch(type)
{
case kCFStreamEventHasBytesAvailable:
{
- dispatch_async(asyncSocket->socketQueue, ^{
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
if (asyncSocket->readStream != stream)
return_from_block;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
@@ -6405,7 +7310,7 @@ static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type
if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
{
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
- [asyncSocket finishSSLHandshake];
+ [asyncSocket cf_finishSSLHandshake];
}
}
else
@@ -6413,66 +7318,57 @@ static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
[asyncSocket doReadData];
}
-
- [pool drain];
- });
+ }});
break;
}
default:
{
- NSError *error = NSMakeCollectable(CFReadStreamCopyError(stream));
+ NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
+
if (error == nil && type == kCFStreamEventEndEncountered)
{
- error = [[asyncSocket connectionClosedError] retain];
+ error = [asyncSocket connectionClosedError];
}
- dispatch_async(asyncSocket->socketQueue, ^{
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - Other");
if (asyncSocket->readStream != stream)
return_from_block;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
- [asyncSocket abortSSLHandshake:error];
+ [asyncSocket cf_abortSSLHandshake:error];
}
else
{
[asyncSocket closeWithError:error];
}
-
- [pool drain];
- });
+ }});
- [error release];
break;
}
}
- [asyncSocket release];
}
static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
{
- GCDAsyncSocket *asyncSocket = [(GCDAsyncSocket *)pInfo retain];
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
switch(type)
{
case kCFStreamEventCanAcceptBytes:
{
- dispatch_async(asyncSocket->socketQueue, ^{
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
if (asyncSocket->writeStream != stream)
return_from_block;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
@@ -6481,7 +7377,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
{
asyncSocket->flags |= kSocketCanAcceptBytes;
- [asyncSocket finishSSLHandshake];
+ [asyncSocket cf_finishSSLHandshake];
}
}
else
@@ -6489,54 +7385,47 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
asyncSocket->flags |= kSocketCanAcceptBytes;
[asyncSocket doWriteData];
}
-
- [pool drain];
- });
+ }});
break;
}
default:
{
- NSError *error = NSMakeCollectable(CFWriteStreamCopyError(stream));
+ NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+
if (error == nil && type == kCFStreamEventEndEncountered)
{
- error = [[asyncSocket connectionClosedError] retain];
+ error = [asyncSocket connectionClosedError];
}
- dispatch_async(asyncSocket->socketQueue, ^{
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFWriteStreamCallback - Other");
if (asyncSocket->writeStream != stream)
return_from_block;
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
- [asyncSocket abortSSLHandshake:error];
+ [asyncSocket cf_abortSSLHandshake:error];
}
else
{
[asyncSocket closeWithError:error];
}
-
- [pool drain];
- });
+ }});
- [error release];
break;
}
}
- [asyncSocket release];
}
- (BOOL)createReadAndWriteStream
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (readStream || writeStream)
@@ -6545,7 +7434,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return YES;
}
- int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
if (socketFD == SOCKET_NULL)
{
@@ -6598,11 +7487,11 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
{
LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ 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 = self;
+ streamContext.info = (__bridge void *)(self);
streamContext.retain = nil;
streamContext.release = nil;
streamContext.copyDescription = nil;
@@ -6632,20 +7521,20 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
- if (!(flags & kAddedStreamListener))
+ if (!(flags & kAddedStreamsToRunLoop))
{
LogVerbose(@"Adding streams to runloop...");
- [[self class] startListenerThreadIfNeeded];
- [[self class] performSelector:@selector(addStreamListener:)
- onThread:listenerThread
+ [[self class] startCFStreamThreadIfNeeded];
+ [[self class] performSelector:@selector(scheduleCFStreams:)
+ onThread:cfstreamThread
withObject:self
waitUntilDone:YES];
- flags |= kAddedStreamListener;
+ flags |= kAddedStreamsToRunLoop;
}
return YES;
@@ -6655,19 +7544,19 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
- if (flags & kAddedStreamListener)
+ if (flags & kAddedStreamsToRunLoop)
{
LogVerbose(@"Removing streams from runloop...");
- [[self class] performSelector:@selector(removeStreamListener:)
- onThread:listenerThread
+ [[self class] performSelector:@selector(unscheduleCFStreams:)
+ onThread:cfstreamThread
withObject:self
waitUntilDone:YES];
- flags &= ~kAddedStreamListener;
+ flags &= ~kAddedStreamsToRunLoop;
}
}
@@ -6675,7 +7564,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
{
LogTrace();
- NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
@@ -6704,14 +7593,85 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
#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
{
- dispatch_sync(socketQueue, block);
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
}
+/**
+ * Questions? Have you read the header file?
+**/
- (int)socketFD
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
@@ -6723,9 +7683,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return socket6FD;
}
+/**
+ * Questions? Have you read the header file?
+**/
- (int)socket4FD
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
@@ -6734,9 +7697,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return socket4FD;
}
+/**
+ * Questions? Have you read the header file?
+**/
- (int)socket6FD
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
@@ -6747,9 +7713,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
#if TARGET_OS_IPHONE
+/**
+ * Questions? Have you read the header file?
+**/
- (CFReadStreamRef)readStream
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
@@ -6761,9 +7730,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return readStream;
}
+/**
+ * Questions? Have you read the header file?
+**/
- (CFWriteStreamRef)writeStream
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
@@ -6806,11 +7778,14 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return YES;
}
+/**
+ * Questions? Have you read the header file?
+**/
- (BOOL)enableBackgroundingOnSocket
{
LogTrace();
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NO;
@@ -6827,7 +7802,7 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
LogTrace();
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NO;
@@ -6836,11 +7811,13 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return [self enableBackgroundingOnSocketWithCaveat:YES];
}
-#else
+#endif
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
- (SSLContextRef)sslContext
{
- if (dispatch_get_current_queue() != socketQueue)
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
@@ -6889,6 +7866,12 @@ static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType ty
return ntohs(pSockaddr6->sin6_port);
}
++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
+{
+ NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
+ return [NSURL fileURLWithPath:path];
+}
+
+ (NSString *)hostFromAddress:(NSData *)address
{
NSString *host;
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud-client.git
More information about the Pkg-owncloud-commits
mailing list