Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
6884 lines (5401 sloc) 179 KB
//
// GCDAsyncSocket.m
//
// This class is in the public domain.
// Originally created by Robbie Hanson in Q4 2010.
// Updated and maintained by Deusty LLC and the Mac development community.
//
// http://code.google.com/p/cocoaasyncsocket/
//
#import "GCDAsyncSocket.h"
#if TARGET_OS_IPHONE
#import <CFNetwork/CFNetwork.h>
#endif
#import <arpa/inet.h>
#import <fcntl.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <netinet/in.h>
#import <net/if.h>
#import <sys/socket.h>
#import <sys/types.h>
#import <sys/ioctl.h>
#import <sys/poll.h>
#import <sys/uio.h>
#import <unistd.h>
#if 0
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
// http://code.google.com/p/cocoalumberjack/
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
#define LogAsync YES
#define LogContext 65535
#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
// Log levels : off, error, warn, info, verbose
static const int logLevel = LOG_LEVEL_VERBOSE;
#else
// Logging Disabled
#define LogError(frmt, ...) {}
#define LogWarn(frmt, ...) {}
#define LogInfo(frmt, ...) {}
#define LogVerbose(frmt, ...) {}
#define LogCError(frmt, ...) {}
#define LogCWarn(frmt, ...) {}
#define LogCInfo(frmt, ...) {}
#define LogCVerbose(frmt, ...) {}
#define LogTrace() {}
#define LogCTrace(frmt, ...) {}
#endif
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
* This makes inline blocks a bit easier to read.
**/
#define return_from_block return
/**
* A socket file descriptor is really just an integer.
* It represents the index of the socket within the kernel.
* This makes invalid file descriptor comparisons easier to read.
**/
#define SOCKET_NULL -1
NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
#if !TARGET_OS_IPHONE
NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
#endif
enum GCDAsyncSocketFlags
{
kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting)
kConnected = 1 << 1, // If set, the socket is connected
kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed
kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout
kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout
kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued
kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued
kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown.
kReadSourceSuspended = 1 << 8, // If set, the read source is suspended
kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended
kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS
kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete
kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete
kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS
kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket
kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained
#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
#endif
};
enum GCDAsyncSocketConfig
{
kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4
kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes
};
#if TARGET_OS_IPHONE
static NSThread *listenerThread; // Used for CFStreams
#endif
@interface GCDAsyncSocket (Private)
// Accepting
- (BOOL)doAccept:(int)socketFD;
// Connecting
- (void)startConnectTimeout:(NSTimeInterval)timeout;
- (void)endConnectTimeout;
- (void)doConnectTimeout;
- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port;
- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
- (void)lookup:(int)aConnectIndex didFail:(NSError *)error;
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr;
- (void)didConnect:(int)aConnectIndex;
- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error;
// Disconnect
- (void)closeWithError:(NSError *)error;
- (void)close;
- (void)maybeClose;
// Errors
- (NSError *)badConfigError:(NSString *)msg;
- (NSError *)badParamError:(NSString *)msg;
- (NSError *)gaiError:(int)gai_error;
- (NSError *)errnoError;
- (NSError *)errnoErrorWithReason:(NSString *)reason;
- (NSError *)connectTimeoutError;
- (NSError *)otherError:(NSString *)msg;
// Diagnostics
- (NSString *)connectedHost4;
- (NSString *)connectedHost6;
- (uint16_t)connectedPort4;
- (uint16_t)connectedPort6;
- (NSString *)localHost4;
- (NSString *)localHost6;
- (uint16_t)localPort4;
- (uint16_t)localPort6;
- (NSString *)connectedHostFromSocket4:(int)socketFD;
- (NSString *)connectedHostFromSocket6:(int)socketFD;
- (uint16_t)connectedPortFromSocket4:(int)socketFD;
- (uint16_t)connectedPortFromSocket6:(int)socketFD;
- (NSString *)localHostFromSocket4:(int)socketFD;
- (NSString *)localHostFromSocket6:(int)socketFD;
- (uint16_t)localPortFromSocket4:(int)socketFD;
- (uint16_t)localPortFromSocket6:(int)socketFD;
// Utilities
- (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr
address6:(NSMutableData **)addr6Ptr
fromDescription:(NSString *)interfaceDescription
port:(uint16_t)port;
- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD;
- (void)suspendReadSource;
- (void)resumeReadSource;
- (void)suspendWriteSource;
- (void)resumeWriteSource;
// Reading
- (void)maybeDequeueRead;
- (void)flushSSLBuffers;
- (void)doReadData;
- (void)doReadEOF;
- (void)completeCurrentRead;
- (void)endCurrentRead;
- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout;
- (void)doReadTimeout;
- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
// Writing
- (void)maybeDequeueWrite;
- (void)doWriteData;
- (void)completeCurrentWrite;
- (void)endCurrentWrite;
- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout;
- (void)doWriteTimeout;
- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
// Security
- (void)maybeStartTLS;
#if !TARGET_OS_IPHONE
- (void)continueSSLHandshake;
#endif
// CFStream
#if TARGET_OS_IPHONE
+ (void)startListenerThreadIfNeeded;
- (BOOL)createReadAndWriteStream;
- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite;
- (BOOL)addStreamsToRunLoop;
- (BOOL)openStreams;
- (void)removeStreamsFromRunLoop;
#endif
// Class Methods
+ (NSString *)hostFromAddress4:(const struct sockaddr_in *)pSockaddr4;
+ (NSString *)hostFromAddress6:(const struct sockaddr_in6 *)pSockaddr6;
+ (uint16_t)portFromAddress4:(const struct sockaddr_in *)pSockaddr4;
+ (uint16_t)portFromAddress6:(const struct sockaddr_in6 *)pSockaddr6;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncReadPacket encompasses the instructions for any given read.
* The content of a read packet allows the code to determine if we're:
* - reading to a certain length
* - reading to a certain separator
* - or simply reading the first chunk of available data
**/
@interface GCDAsyncReadPacket : NSObject
{
@public
NSMutableData *buffer;
NSUInteger startOffset;
NSUInteger bytesDone;
NSUInteger maxLength;
NSTimeInterval timeout;
NSUInteger readLength;
NSData *term;
BOOL bufferOwner;
NSUInteger originalBufferLength;
long tag;
}
- (id)initWithData:(NSMutableData *)d
startOffset:(NSUInteger)s
maxLength:(NSUInteger)m
timeout:(NSTimeInterval)t
readLength:(NSUInteger)l
terminator:(NSData *)e
tag:(long)i;
- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr;
- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
@end
@implementation GCDAsyncReadPacket
- (id)initWithData:(NSMutableData *)d
startOffset:(NSUInteger)s
maxLength:(NSUInteger)m
timeout:(NSTimeInterval)t
readLength:(NSUInteger)l
terminator:(NSData *)e
tag:(long)i
{
if((self = [super init]))
{
bytesDone = 0;
maxLength = m;
timeout = t;
readLength = l;
term = [e copy];
tag = i;
if (d)
{
buffer = [d retain];
startOffset = s;
bufferOwner = NO;
originalBufferLength = [d length];
}
else
{
if (readLength > 0)
buffer = [[NSMutableData alloc] initWithLength:readLength];
else
buffer = [[NSMutableData alloc] initWithLength:0];
startOffset = 0;
bufferOwner = YES;
originalBufferLength = 0;
}
}
return self;
}
/**
* Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
**/
- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
NSUInteger buffSpace = buffSize - buffUsed;
if (bytesToRead > buffSpace)
{
NSUInteger buffInc = bytesToRead - buffSpace;
[buffer increaseLengthBy:buffInc];
}
}
/**
* This method is used when we do NOT know how much data is available to be read from the socket.
* This method returns the default value unless it exceeds the specified readLength or maxLength.
*
* Furthermore, the shouldPreBuffer decision is based upon the packet type,
* and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
**/
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSUInteger result;
if (readLength > 0)
{
// Read a specific length of data
result = MIN(defaultValue, (readLength - bytesDone));
// There is no need to prebuffer since we know exactly how much data we need to read.
// Even if the buffer isn't currently big enough to fit this amount of data,
// it would have to be resized eventually anyway.
if (shouldPreBufferPtr)
*shouldPreBufferPtr = NO;
}
else
{
// Either reading until we find a specified terminator,
// or we're simply reading all available data.
//
// In other words, one of:
//
// - readDataToData packet
// - readDataWithTimeout packet
if (maxLength > 0)
result = MIN(defaultValue, (maxLength - bytesDone));
else
result = defaultValue;
// Since we don't know the size of the read in advance,
// the shouldPreBuffer decision is based upon whether the returned value would fit
// in the current buffer without requiring a resize of the buffer.
//
// This is because, in all likelyhood, the amount read from the socket will be less than the default value.
// Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
if (shouldPreBufferPtr)
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
NSUInteger buffSpace = buffSize - buffUsed;
if (buffSpace >= result)
*shouldPreBufferPtr = NO;
else
*shouldPreBufferPtr = YES;
}
}
return result;
}
/**
* For read packets without a set terminator, returns the amount of data
* that can be read without exceeding the readLength or maxLength.
*
* The given parameter indicates the number of bytes estimated to be available on the socket,
* which is taken into consideration during the calculation.
*
* The given hint MUST be greater than zero.
**/
- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
{
NSAssert(term == nil, @"This method does not apply to term reads");
NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
if (readLength > 0)
{
// Read a specific length of data
return MIN(bytesAvailable, (readLength - bytesDone));
// No need to avoid resizing the buffer.
// If the user provided their own buffer,
// and told us to read a certain length of data that exceeds the size of the buffer,
// then it is clear that our code will resize the buffer during the read operation.
//
// This method does not actually do any resizing.
// The resizing will happen elsewhere if needed.
}
else
{
// Read all available data
NSUInteger result = bytesAvailable;
if (maxLength > 0)
{
result = MIN(result, (maxLength - bytesDone));
}
// No need to avoid resizing the buffer.
// If the user provided their own buffer,
// and told us to read all available data without giving us a maxLength,
// then it is clear that our code might resize the buffer during the read operation.
//
// This method does not actually do any resizing.
// The resizing will happen elsewhere if needed.
return result;
}
}
/**
* For read packets with a set terminator, returns the amount of data
* that can be read without exceeding the maxLength.
*
* The given parameter indicates the number of bytes estimated to be available on the socket,
* which is taken into consideration during the calculation.
*
* To optimize memory allocations, mem copies, and mem moves
* the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
* or if the data can be read directly into the read packet's buffer.
**/
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
NSUInteger result = bytesAvailable;
if (maxLength > 0)
{
result = MIN(result, (maxLength - bytesDone));
}
// Should the data be read into the read packet's buffer, or into a pre-buffer first?
//
// One would imagine the preferred option is the faster one.
// So which one is faster?
//
// Reading directly into the packet's buffer requires:
// 1. Possibly resizing packet buffer (malloc/realloc)
// 2. Filling buffer (read)
// 3. Searching for term (memcmp)
// 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
//
// Reading into prebuffer first:
// 1. Possibly resizing prebuffer (malloc/realloc)
// 2. Filling buffer (read)
// 3. Searching for term (memcmp)
// 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
// 5. Removing underflow from prebuffer (memmove)
//
// Comparing the performance of the two we can see that reading
// data into the prebuffer first is slower due to the extra memove.
//
// However:
// The implementation of NSMutableData is open source via core foundation's CFMutableData.
// Decreasing the length of a mutable data object doesn't cause a realloc.
// In other words, the capacity of a mutable data object can grow, but doesn't shrink.
//
// This means the prebuffer will rarely need a realloc.
// The packet buffer, on the other hand, may often need a realloc.
// This is especially true if we are the buffer owner.
// Furthermore, if we are constantly realloc'ing the packet buffer,
// and then moving the overflow into the prebuffer,
// then we're consistently over-allocating memory for each term read.
// And now we get into a bit of a tradeoff between speed and memory utilization.
//
// The end result is that the two perform very similarly.
// And we can answer the original question very simply by another means.
//
// If we can read all the data directly into the packet's buffer without resizing it first,
// then we do so. Otherwise we use the prebuffer.
if (shouldPreBufferPtr)
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
if ((buffSize - buffUsed) >= result)
*shouldPreBufferPtr = NO;
else
*shouldPreBufferPtr = YES;
}
return result;
}
/**
* For read packets with a set terminator,
* returns the amount of data that can be read from the given preBuffer,
* without going over a terminator or the maxLength.
*
* It is assumed the terminator has not already been read.
**/
- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!");
// We know that the terminator, as a whole, doesn't exist in our own buffer.
// But it is possible that a portion of it exists in our buffer.
// So we're going to look for the terminator starting with a portion of our own buffer.
//
// Example:
//
// term length = 3 bytes
// bytesDone = 5 bytes
// preBuffer length = 5 bytes
//
// If we append the preBuffer to our buffer,
// it would look like this:
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------------------
//
// So we start our search here:
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// -------^-^-^---------
//
// And move forwards...
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------^-^-^-------
//
// Until we find the terminator or reach the end.
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------------^-^-^-
BOOL found = NO;
NSUInteger termLength = [term length];
NSUInteger preBufferLength = [preBuffer length];
if ((bytesDone + preBufferLength) < termLength)
{
// Not enough data for a full term sequence yet
return preBufferLength;
}
NSUInteger maxPreBufferLength;
if (maxLength > 0) {
maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
// Note: maxLength >= termLength
}
else {
maxPreBufferLength = preBufferLength;
}
uint8_t seq[termLength];
const void *termBuf = [term bytes];
NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
NSUInteger preLen = termLength - bufLen;
const uint8_t *pre = [preBuffer bytes];
NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
NSUInteger result = preBufferLength;
NSUInteger i;
for (i = 0; i < loopCount; i++)
{
if (bufLen > 0)
{
// Combining bytes from buffer and preBuffer
memcpy(seq, buf, bufLen);
memcpy(seq + bufLen, pre, preLen);
if (memcmp(seq, termBuf, termLength) == 0)
{
result = preLen;
found = YES;
break;
}
buf++;
bufLen--;
preLen++;
}
else
{
// Comparing directly from preBuffer
if (memcmp(pre, termBuf, termLength) == 0)
{
NSUInteger preOffset = pre - (const uint8_t *)[preBuffer bytes]; // pointer arithmetic
result = preOffset + termLength;
found = YES;
break;
}
pre++;
}
}
// There is no need to avoid resizing the buffer in this particular situation.
if (foundPtr) *foundPtr = found;
return result;
}
/**
* For read packets with a set terminator, scans the packet buffer for the term.
* It is assumed the terminator had not been fully read prior to the new bytes.
*
* If the term is found, the number of excess bytes after the term are returned.
* If the term is not found, this method will return -1.
*
* Note: A return value of zero means the term was found at the very end.
*
* Prerequisites:
* The given number of bytes have been added to the end of our buffer.
* Our bytesDone variable has NOT been changed due to the prebuffered bytes.
**/
- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
// The implementation of this method is very similar to the above method.
// See the above method for a discussion of the algorithm used here.
uint8_t *buff = [buffer mutableBytes];
NSUInteger buffLength = bytesDone + numBytes;
const void *termBuff = [term bytes];
NSUInteger termLength = [term length];
// Note: We are dealing with unsigned integers,
// so make sure the math doesn't go below zero.
NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
while (i + termLength <= buffLength)
{
uint8_t *subBuffer = buff + startOffset + i;
if (memcmp(subBuffer, termBuff, termLength) == 0)
{
return buffLength - (i + termLength);
}
i++;
}
return -1;
}
- (void)dealloc
{
[buffer release];
[term release];
[super dealloc];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncWritePacket encompasses the instructions for any given write.
**/
@interface GCDAsyncWritePacket : NSObject
{
@public
NSData *buffer;
NSUInteger bytesDone;
long tag;
NSTimeInterval timeout;
}
- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
@end
@implementation GCDAsyncWritePacket
- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
if((self = [super init]))
{
buffer = [d retain];
bytesDone = 0;
timeout = t;
tag = i;
}
return self;
}
- (void)dealloc
{
[buffer release];
[super dealloc];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
* This class my be altered to support more than just TLS in the future.
**/
@interface GCDAsyncSpecialPacket : NSObject
{
@public
NSDictionary *tlsSettings;
}
- (id)initWithTLSSettings:(NSDictionary *)settings;
@end
@implementation GCDAsyncSpecialPacket
- (id)initWithTLSSettings:(NSDictionary *)settings
{
if((self = [super init]))
{
tlsSettings = [settings copy];
}
return self;
}
- (void)dealloc
{
[tlsSettings release];
[super dealloc];
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation GCDAsyncSocket
- (id)init
{
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}
- (id)initWithSocketQueue:(dispatch_queue_t)sq
{
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}
- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
if (dq)
{
dispatch_retain(dq);
delegateQueue = dq;
}
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
connectIndex = 0;
if (sq)
{
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
dispatch_retain(sq);
socketQueue = sq;
}
else
{
socketQueue = dispatch_queue_create("GCDAsyncSocket", NULL);
}
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
partialReadBuffer = [[NSMutableData alloc] init];
}
return self;
}
- (void)dealloc
{
LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
if (dispatch_get_current_queue() == socketQueue)
{
[self closeWithError:nil];
}
else
{
dispatch_sync(socketQueue, ^{
[self closeWithError:nil];
});
}
delegate = nil;
if (delegateQueue)
dispatch_release(delegateQueue);
delegateQueue = NULL;
dispatch_release(socketQueue);
socketQueue = NULL;
[readQueue release];
[writeQueue release];
[partialReadBuffer release];
#if !TARGET_OS_IPHONE
[sslReadBuffer release];
#endif
[userData release];
LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
[super dealloc];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)delegate
{
if (dispatch_get_current_queue() == socketQueue)
{
return delegate;
}
else
{
__block id result;
dispatch_sync(socketQueue, ^{
result = delegate;
});
return result;
}
}
- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
delegate = newDelegate;
};
if (dispatch_get_current_queue() == socketQueue) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:YES];
}
- (dispatch_queue_t)delegateQueue
{
if (dispatch_get_current_queue() == socketQueue)
{
return delegateQueue;
}
else
{
__block dispatch_queue_t result;
dispatch_sync(socketQueue, ^{
result = delegateQueue;
});
return result;
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
if (delegateQueue)
dispatch_release(delegateQueue);
if (newDelegateQueue)
dispatch_retain(newDelegateQueue);
delegateQueue = newDelegateQueue;
};
if (dispatch_get_current_queue() == socketQueue) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:YES];
}
- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
{
if (dispatch_get_current_queue() == socketQueue)
{
if (delegatePtr) *delegatePtr = delegate;
if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
}
else
{
__block id dPtr = NULL;
__block dispatch_queue_t dqPtr = NULL;
dispatch_sync(socketQueue, ^{
dPtr = delegate;
dqPtr = delegateQueue;
});
if (delegatePtr) *delegatePtr = dPtr;
if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
delegate = newDelegate;
if (delegateQueue)
dispatch_release(delegateQueue);
if (newDelegateQueue)
dispatch_retain(newDelegateQueue);
delegateQueue = newDelegateQueue;
};
if (dispatch_get_current_queue() == socketQueue) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}
- (BOOL)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)
{
return ((config & kIPv4Disabled) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((config & kIPv4Disabled) == 0);
});
return result;
}
}
- (void)setIPv4Enabled:(BOOL)flag
{
// Note: YES means kIPv4Disabled is OFF
dispatch_block_t block = ^{
if (flag)
config &= ~kIPv4Disabled;
else
config |= kIPv4Disabled;
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv6Enabled
{
// Note: YES means kIPv6Disabled is OFF
if (dispatch_get_current_queue() == socketQueue)
{
return ((config & kIPv6Disabled) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((config & kIPv6Disabled) == 0);
});
return result;
}
}
- (void)setIPv6Enabled:(BOOL)flag
{
// Note: YES means kIPv6Disabled is OFF
dispatch_block_t block = ^{
if (flag)
config &= ~kIPv6Disabled;
else
config |= kIPv6Disabled;
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv4PreferredOverIPv6
{
// Note: YES means kPreferIPv6 is OFF
if (dispatch_get_current_queue() == socketQueue)
{
return ((config & kPreferIPv6) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((config & kPreferIPv6) == 0);
});
return result;
}
}
- (void)setPreferIPv4OverIPv6:(BOOL)flag
{
// Note: YES means kPreferIPv6 is OFF
dispatch_block_t block = ^{
if (flag)
config &= ~kPreferIPv6;
else
config |= kPreferIPv6;
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_async(socketQueue, block);
}
- (id)userData
{
__block id result;
dispatch_block_t block = ^{
result = [userData retain];
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
return [result autorelease];
}
- (void)setUserData:(id)arbitraryUserData
{
dispatch_block_t block = ^{
if (userData != arbitraryUserData)
{
[userData release];
userData = [arbitraryUserData retain];
}
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_async(socketQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Accepting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
{
return [self acceptOnInterface:nil port:port error:errPtr];
}
- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
// CreateSocket Block
// This block will be invoked within the dispatch block below.
int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
int socketFD = socket(domain, SOCK_STREAM, 0);
if (socketFD == SOCKET_NULL)
{
NSString *reason = @"Error in socket() function";
err = [[self errnoErrorWithReason:reason] retain];
return SOCKET_NULL;
}
int status;
// Set socket options
status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (status == -1)
{
NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
err = [[self errnoErrorWithReason:reason] retain];
close(socketFD);
return SOCKET_NULL;
}
int reuseOn = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
if (status == -1)
{
NSString *reason = @"Error enabling address reuse (setsockopt)";
err = [[self errnoErrorWithReason:reason] retain];
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] retain];
close(socketFD);
return SOCKET_NULL;
}
// Listen
status = listen(socketFD, 1024);
if (status == -1)
{
NSString *reason = @"Error in listen() function";
err = [[self errnoErrorWithReason:reason] retain];
close(socketFD);
return SOCKET_NULL;
}
return socketFD;
};
// Create dispatch block and run on socketQueue
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (delegate == nil) // Must have delegate set
{
NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
err = [[self badConfigError:msg] retain];
[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];
[pool drain];
return_from_block;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
err = [[self badConfigError:msg] retain];
[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];
[pool drain];
return_from_block;
}
// Clear queues (spurious read/write requests post disconnect)
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
// Resolve interface from description
NSMutableData *interface4 = nil;
NSMutableData *interface6 = nil;
[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
if ((interface4 == nil) && (interface6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [[self badParamError:msg] retain];
[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];
[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];
[pool drain];
return_from_block;
}
BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
// Create sockets, configure, bind, and listen
if (enableIPv4)
{
LogVerbose(@"Creating IPv4 socket");
socket4FD = createSocket(AF_INET, interface4);
if (socket4FD == SOCKET_NULL)
{
[pool drain];
return_from_block;
}
}
if (enableIPv6)
{
LogVerbose(@"Creating IPv6 socket");
if (enableIPv4 && (port == 0))
{
// No specific port was specified, so we allowed the OS to pick an available port for us.
// Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
addr6->sin6_port = htons([self localPort4]);
}
socket6FD = createSocket(AF_INET6, interface6);
if (socket6FD == SOCKET_NULL)
{
if (socket4FD != SOCKET_NULL)
{
close(socket4FD);
}
[pool drain];
return_from_block;
}
}
// Create accept sources
if (enableIPv4)
{
accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
int socketFD = socket4FD;
dispatch_source_t acceptSource = accept4Source;
dispatch_source_set_event_handler(accept4Source, ^{
NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
LogVerbose(@"event4Block");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([self doAccept:socketFD] && (++i < numPendingConnections));
[eventPool drain];
});
dispatch_source_set_cancel_handler(accept4Source, ^{
LogVerbose(@"dispatch_release(accept4Source)");
dispatch_release(acceptSource);
LogVerbose(@"close(socket4FD)");
close(socketFD);
});
LogVerbose(@"dispatch_resume(accept4Source)");
dispatch_resume(accept4Source);
}
if (enableIPv6)
{
accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
int socketFD = socket6FD;
dispatch_source_t acceptSource = accept6Source;
dispatch_source_set_event_handler(accept6Source, ^{
NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
LogVerbose(@"event6Block");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([self doAccept:socketFD] && (++i < numPendingConnections));
[eventPool drain];
});
dispatch_source_set_cancel_handler(accept6Source, ^{
LogVerbose(@"dispatch_release(accept6Source)");
dispatch_release(acceptSource);
LogVerbose(@"close(socket6FD)");
close(socketFD);
});
LogVerbose(@"dispatch_resume(accept6Source)");
dispatch_resume(accept6Source);
}
flags |= kSocketStarted;
result = YES;
[pool drain];
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
LogInfo(@"Error in accept: %@", err);
if (errPtr)
*errPtr = [err autorelease];
else
[err release];
}
return result;
}
- (BOOL)doAccept:(int)parentSocketFD
{
LogTrace();
BOOL isIPv4;
int childSocketFD;
NSData *childSocketAddress;
if (parentSocketFD == socket4FD)
{
isIPv4 = YES;
struct sockaddr_in addr;
socklen_t addrLen = sizeof(addr);
childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
if (childSocketFD == -1)
{
LogWarn(@"Accept failed with error: %@", [self errnoError]);
return NO;
}
childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
}
else // if (parentSocketFD == socket6FD)
{
isIPv4 = NO;
struct sockaddr_in6 addr;
socklen_t addrLen = sizeof(addr);
childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
if (childSocketFD == -1)
{
LogWarn(@"Accept failed with error: %@", [self errnoError]);
return NO;
}
childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
}
// Enable non-blocking IO on the socket
int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
if (result == -1)
{
LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
return NO;
}
// Prevent SIGPIPE signals
int nosigpipe = 1;
setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
// Notify delegate
if (delegateQueue)
{
id theDelegate = delegate;
dispatch_async(delegateQueue, ^{
NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
// Query delegate for custom socket queue
dispatch_queue_t childSocketQueue = NULL;
if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
{
childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
onSocket:self];
}
// Create GCDAsyncSocket instance for accepted socket
GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:delegate
delegateQueue:delegateQueue
socketQueue:childSocketQueue];
if (isIPv4)
acceptedSocket->socket4FD = childSocketFD;
else
acceptedSocket->socket6FD = childSocketFD;
acceptedSocket->flags = (kSocketStarted | kConnected);
// Setup read and write sources for accepted socket
dispatch_async(acceptedSocket->socketQueue, ^{
NSAutoreleasePool *socketPool = [[NSAutoreleasePool alloc] init];
[acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
[socketPool drain];
});
// Notify delegate
if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
{
[theDelegate socket:self didAcceptNewSocket:acceptedSocket];
}
// Release the socket queue returned from the delegate (it was retained by acceptedSocket)
if (childSocketQueue)
dispatch_release(childSocketQueue);
// Release the accepted socket (it should have been retained by the delegate)
[acceptedSocket release];
[delegatePool drain];
});
}
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Connecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a connection attempt.
* It is shared between the connectToHost and connectToAddress methods.
*
**/
- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
{
NSAssert(dispatch_get_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;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (interface)
{
NSMutableData *interface4 = nil;
NSMutableData *interface6 = nil;
[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
if ((interface4 == nil) && (interface6 == nil))
{
if (errPtr)
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
*errPtr = [self badParamError:msg];
}
return NO;
}
if (isIPv4Disabled && (interface6 == nil))
{
if (errPtr)
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
*errPtr = [self badParamError:msg];
}
return NO;
}
if (isIPv6Disabled && (interface4 == nil))
{
if (errPtr)
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
*errPtr = [self badParamError:msg];
}
return NO;
}
connectInterface4 = [interface4 retain];
connectInterface6 = [interface6 retain];
}
// Clear queues (spurious read/write requests post disconnect)
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
return YES;
}
- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
{
return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
}
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
}
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
viaInterface:(NSString *)interface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Check for problems with host parameter
if (host == nil)
{
NSString *msg = @"Invalid host parameter (nil). Should be a domain name or IP address string.";
err = [[self badParamError:msg] retain];
[pool drain];
return_from_block;
}
// Run through standard pre-connect checks
if (![self preConnectWithInterface:interface error:&err])
{
[err retain];
[pool drain];
return_from_block;
}
// We've made it past all the checks.
// It's time to start the connection process.
flags |= kSocketStarted;
LogVerbose(@"Dispatching DNS lookup...");
// It's possible that the given host parameter is actually a NSMutableString.
// So we want to copy it now, within this block that will be executed synchronously.
// This way the asynchronous lookup block below doesn't have to worry about it changing.
int aConnectIndex = connectIndex;
NSString *hostCpy = [[host copy] autorelease];
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{
NSAutoreleasePool *lookupPool = [[NSAutoreleasePool alloc] init];
[self lookup:aConnectIndex host:hostCpy port:port];
[lookupPool drain];
});
[self startConnectTimeout:timeout];
result = YES;
[pool drain];
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
if (errPtr)
*errPtr = [err autorelease];
else
[err release];
}
return result;
}
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
{
return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
}
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
{
return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
}
- (BOOL)connectToAddress:(NSData *)remoteAddr
viaInterface:(NSString *)interface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Check for problems with remoteAddr parameter
NSData *address4 = nil;
NSData *address6 = nil;
if ([remoteAddr length] >= sizeof(struct sockaddr))
{
const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
if (sockaddr->sa_family == AF_INET)
{
if ([remoteAddr length] == sizeof(struct sockaddr_in))
{
address4 = remoteAddr;
}
}
else if (sockaddr->sa_family == AF_INET6)
{
if ([remoteAddr length] == sizeof(struct sockaddr_in6))
{
address6 = remoteAddr;
}
}
}
if ((address4 == nil) && (address6 == nil))
{
NSString *msg = @"A valid IPv4 or IPv6 address was not given";
err = [[self badParamError:msg] retain];
[pool drain];
return_from_block;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address4 != nil))
{
NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
err = [[self badParamError:msg] retain];
[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];
[pool drain];
return_from_block;
}
// Run through standard pre-connect checks
if (![self preConnectWithInterface:interface error:&err])
{
[err retain];
[pool drain];
return_from_block;
}
// We've made it past all the checks.
// It's time to start the connection process.
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[err retain];
[pool drain];
return_from_block;
}
flags |= kSocketStarted;
[self startConnectTimeout:timeout];
result = YES;
[pool drain];
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
if (errPtr)
*errPtr = [err autorelease];
else
[err release];
}
return result;
}
- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port
{
LogTrace();
// This method is executed on a global concurrent queue.
// It posts the results back to the socket queue.
// The lookupIndex is used to ignore the results if the connect operation was cancelled or timed out.
NSError *error = nil;
NSData *address4 = nil;
NSData *address6 = nil;
if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
{
// Use LOOPBACK address
struct sockaddr_in nativeAddr;
nativeAddr.sin_len = sizeof(struct sockaddr_in);
nativeAddr.sin_family = AF_INET;
nativeAddr.sin_port = htons(port);
nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
struct sockaddr_in6 nativeAddr6;
nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
nativeAddr6.sin6_family = AF_INET6;
nativeAddr6.sin6_port = htons(port);
nativeAddr6.sin6_flowinfo = 0;
nativeAddr6.sin6_addr = in6addr_loopback;
nativeAddr6.sin6_scope_id = 0;
// Wrap the native address structures
address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
if (gai_error)
{
error = [self gaiError:gai_error];
}
else
{
for(res = res0; res; res = res->ai_next)
{
if ((address4 == nil) && (res->ai_family == AF_INET))
{
// Found IPv4 address
// Wrap the native address structure
address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
else if ((address6 == nil) && (res->ai_family == AF_INET6))
{
// Found IPv6 address
// Wrap the native address structure
address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
}
freeaddrinfo(res0);
if ((address4 == nil) && (address6 == nil))
{
error = [self gaiError:EAI_FAIL];
}
}
}
if (error)
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self lookup:aConnectIndex didFail:error];
[pool drain];
});
}
else
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6];
[pool drain];
});
}
}
- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
NSAssert(address4 || address6, @"Expected at least one valid address");
if (aConnectIndex != connectIndex)
{
LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
// Check for problems
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address6 == nil))
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
[self closeWithError:[self otherError:msg]];
return;
}
if (isIPv6Disabled && (address4 == nil))
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
[self closeWithError:[self otherError:msg]];
return;
}
// Start the normal connection process
NSError *err = nil;
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[self closeWithError:err];
}
}
/**
* This method is called if the DNS lookup fails.
* This method is executed on the socketQueue.
*
* Since the DNS lookup executed synchronously on a global concurrent queue,
* the original connection request may have already been cancelled or timed-out by the time this method is invoked.
* The lookupIndex tells us whether the lookup is still valid or not.
**/
- (void)lookup:(int)aConnectIndex didFail:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
if (aConnectIndex != connectIndex)
{
LogInfo(@"Ignoring lookup:didFail: - already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
[self endConnectTimeout];
[self closeWithError:error];
}
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
// Determine socket type
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
// Create the socket
int socketFD;
NSData *address;
NSData *connectInterface;
if (useIPv6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = socket(AF_INET6, SOCK_STREAM, 0);
socketFD = socket6FD;
address = address6;
connectInterface = connectInterface6;
}
else
{
LogVerbose(@"Creating IPv4 socket");
socket4FD = socket(AF_INET, SOCK_STREAM, 0);
socketFD = socket4FD;
address = address4;
connectInterface = connectInterface4;
}
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return NO;
}
// Bind the socket to the desired interface (if needed)
if (connectInterface)
{
LogVerbose(@"Binding socket...");
if ([[self class] portFromAddress:connectInterface] > 0)
{
// Since we're going to be binding to a specific port,
// we should turn on reuseaddr to allow us to override sockets in time_wait.
int reuseOn = 1;
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
}
const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
if (result != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
return NO;
}
}
// Start the connection process in a background queue
int aConnectIndex = connectIndex;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
if (result == 0)
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self didConnect:aConnectIndex];
[pool drain];
});
}
else
{
NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self didNotConnect:aConnectIndex error:error];
[pool drain];
});
}
});
LogVerbose(@"Connecting...");
return YES;
}
- (void)didConnect:(int)aConnectIndex
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
if (aConnectIndex != connectIndex)
{
LogInfo(@"Ignoring didConnect, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
flags |= kConnected;
[self endConnectTimeout];
aConnectIndex = connectIndex;
// Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
//
// Note:
// There may be configuration options that must be set by the delegate before opening the streams.
// The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
//
// Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
// This gives the delegate time to properly configure the streams if needed.
dispatch_block_t SetupStreamsPart1 = ^{
#if TARGET_OS_IPHONE
if (![self createReadAndWriteStream])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
if (![self registerForStreamCallbacksIncludingReadWrite:NO])
{
[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
return;
}
#endif
};
dispatch_block_t SetupStreamsPart2 = ^{
#if TARGET_OS_IPHONE
if (aConnectIndex != connectIndex)
{
// The socket has been disconnected.
return;
}
if (![self addStreamsToRunLoop])
{
[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
return;
}
if (![self openStreams])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
#endif
};
// Notify delegate
NSString *host = [self connectedHost];
uint16_t port = [self connectedPort];
if (delegateQueue && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
{
SetupStreamsPart1();
id theDelegate = delegate;
dispatch_async(delegateQueue, ^{
NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
[theDelegate socket:self didConnectToHost:host port:port];
dispatch_async(socketQueue, ^{
NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
SetupStreamsPart2();
[callbackPool drain];
});
[delegatePool drain];
});
}
else
{
SetupStreamsPart1();
SetupStreamsPart2();
}
// Get the connected socket
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD;
// Enable non-blocking IO on the socket
int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (result == -1)
{
NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
[self closeWithError:[self otherError:errMsg]];
return;
}
// Prevent SIGPIPE signals
int nosigpipe = 1;
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
// Setup our read/write sources
[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
// Dequeue any pending read/write requests
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
if (aConnectIndex != connectIndex)
{
LogInfo(@"Ignoring didNotConnect, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
[self endConnectTimeout];
[self closeWithError:error];
}
- (void)startConnectTimeout:(NSTimeInterval)timeout
{
if (timeout >= 0.0)
{
connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
dispatch_source_set_event_handler(connectTimer, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self doConnectTimeout];
[pool drain];
});
dispatch_source_t theConnectTimer = connectTimer;
dispatch_source_set_cancel_handler(connectTimer, ^{
LogVerbose(@"dispatch_release(connectTimer)");
dispatch_release(theConnectTimer);
});
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(connectTimer);
}
}
- (void)endConnectTimeout
{
LogTrace();
if (connectTimer)
{
dispatch_source_cancel(connectTimer);
connectTimer = NULL;
}
// Increment connectIndex.
// This will prevent us from processing results from any related background asynchronous operations.
//
// Note: This should be called from close method even if connectTimer is NULL.
// This is because one might disconnect a socket prior to a successful connection which had no timeout.
connectIndex++;
if (connectInterface4)
{
[connectInterface4 release];
connectInterface4 = nil;
}
if (connectInterface6)
{
[connectInterface6 release];
connectInterface6 = nil;
}
}
- (void)doConnectTimeout
{
LogTrace();
[self endConnectTimeout];
[self closeWithError:[self connectTimeoutError]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Disconnecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)closeWithError:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
[self endConnectTimeout];
if (currentRead != nil) [self endCurrentRead];
if (currentWrite != nil) [self endCurrentWrite];
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
[partialReadBuffer setLength:0];
#if TARGET_OS_IPHONE
{
if (readStream || writeStream)
{
[self removeStreamsFromRunLoop];
if (readStream)
{
CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;
}
if (writeStream)
{
CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;
}
}
}
#else
{
[sslReadBuffer setLength:0];
if (sslContext)
{
// Getting a linker error here about SSLDisposeContext?
// You need to add the Security Framework to your application.
SSLDisposeContext(sslContext);
sslContext = NULL;
}
}
#endif
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
if (accept4Source)
{
LogVerbose(@"dispatch_source_cancel(accept4Source)");
dispatch_source_cancel(accept4Source);
// We never suspend accept4Source
accept4Source = NULL;
}
if (accept6Source)
{
LogVerbose(@"dispatch_source_cancel(accept6Source)");
dispatch_source_cancel(accept6Source);
// We never suspend accept6Source
accept6Source = NULL;
}
if (readSource)
{
LogVerbose(@"dispatch_source_cancel(readSource)");
dispatch_source_cancel(readSource);
[self resumeReadSource];
readSource = NULL;
}
if (writeSource)
{
LogVerbose(@"dispatch_source_cancel(writeSource)");
dispatch_source_cancel(writeSource);
[self resumeWriteSource];
writeSource = NULL;
}
// The sockets will be closed by the cancel handlers of the corresponding source
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
// If the client has passed the connect/accept method, then the connection has at least begun.
// Notify delegate that it is now ending.
BOOL shouldCallDelegate = (flags & kSocketStarted);
// Clear stored socket info and all flags (config remains as is)
socketFDBytesAvailable = 0;
flags = 0;
if (shouldCallDelegate)
{
if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
{
id theDelegate = delegate;
dispatch_async(delegateQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[theDelegate socketDidDisconnect:self withError:error];
[pool drain];
});
}
}
}
- (void)disconnect
{
dispatch_block_t block = ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (flags & kSocketStarted)
{
[self closeWithError:nil];
}
[pool drain];
};
// Synchronous disconnection, as documented in the header file
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
}
- (void)disconnectAfterReading
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterReads);
[self maybeClose];
}
[pool drain];
});
}
- (void)disconnectAfterWriting
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
[self maybeClose];
}
[pool drain];
});
}
- (void)disconnectAfterReadingAndWriting
{
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (flags & kSocketStarted)
{
flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
[self maybeClose];
}
[pool drain];
});
}
/**
* Closes the socket if possible.
* That is, if all writes have completed, and we're set to disconnect after writing,
* or if all reads have completed, and we're set to disconnect after reading.
**/
- (void)maybeClose
{
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
BOOL shouldClose = NO;
if (flags & kDisconnectAfterReads)
{
if (([readQueue count] == 0) && (currentRead == nil))
{
if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
shouldClose = YES;
}
}
else
{
shouldClose = YES;
}
}
}
else if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
shouldClose = YES;
}
}
if (shouldClose)
{
[self closeWithError:nil];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Errors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSError *)badConfigError:(NSString *)errMsg
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
}
- (NSError *)badParamError:(NSString *)errMsg
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
}
- (NSError *)gaiError:(int)gai_error
{
NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
}
- (NSError *)errnoErrorWithReason:(NSString *)reason
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
reason, NSLocalizedFailureReasonErrorKey, nil];
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}
- (NSError *)errnoError
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}
- (NSError *)sslError:(OSStatus)ssl_error
{
NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey];
return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
}
- (NSError *)connectTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Attempt to connect to host timed out", nil);
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
}
/**
* Returns a standard AsyncSocket maxed out error.
**/
- (NSError *)readMaxedOutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Read operation reached set maximum length", nil);
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
}
/**
* Returns a standard AsyncSocket write timeout error.
**/
- (NSError *)readTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Read operation timed out", nil);
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
}
/**
* Returns a standard AsyncSocket write timeout error.
**/
- (NSError *)writeTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Write operation timed out", nil);
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
}
- (NSError *)connectionClosedError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Socket closed by remote peer", nil);
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
}
- (NSError *)otherError:(NSString *)errMsg
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Diagnostics
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)isDisconnected
{
__block BOOL result;
dispatch_block_t block = ^{
result = (flags & kSocketStarted) ? NO : YES;
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isConnected
{
__block BOOL result;
dispatch_block_t block = ^{
result = (flags & kConnected) ? YES : NO;
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (NSString *)connectedHost
{
if (dispatch_get_current_queue() == socketQueue)
{
if (socket4FD != SOCKET_NULL)
return [self connectedHostFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self connectedHostFromSocket6:socket6FD];
return nil;
}
else
{
__block NSString *result = nil;
dispatch_sync(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (socket4FD != SOCKET_NULL)
result = [[self connectedHostFromSocket4:socket4FD] retain];
else if (socket6FD != SOCKET_NULL)
result = [[self connectedHostFromSocket6:socket6FD] retain];
[pool drain];
});
return [result autorelease];
}
}
- (uint16_t)connectedPort
{
if (dispatch_get_current_queue() == socketQueue)
{
if (socket4FD != SOCKET_NULL)
return [self connectedPortFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self connectedPortFromSocket6:socket6FD];
return 0;
}
else
{
__block uint16_t result = 0;
dispatch_sync(socketQueue, ^{
// No need for autorelease pool
if (socket4FD != SOCKET_NULL)
result = [self connectedPortFromSocket4:socket4FD];
else if (socket6FD != SOCKET_NULL)
result = [self connectedPortFromSocket6:socket6FD];
});
return result;
}
}
- (NSString *)localHost
{
if (dispatch_get_current_queue() == socketQueue)
{
if (socket4FD != SOCKET_NULL)
return [self localHostFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self localHostFromSocket6:socket6FD];
return nil;
}
else
{
__block NSString *result = nil;
dispatch_sync(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (socket4FD != SOCKET_NULL)
result = [[self localHostFromSocket4:socket4FD] retain];
else if (socket6FD != SOCKET_NULL)
result = [[self localHostFromSocket6:socket6FD] retain];
[pool drain];
});
return [result autorelease];
}
}
- (uint16_t)localPort
{
if (dispatch_get_current_queue() == socketQueue)
{
if (socket4FD != SOCKET_NULL)
return [self localPortFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self localPortFromSocket6:socket6FD];
return 0;
}
else
{
__block uint16_t result = 0;
dispatch_sync(socketQueue, ^{
// No need for autorelease pool
if (socket4FD != SOCKET_NULL)
result = [self localPortFromSocket4:socket4FD];
else if (socket6FD != SOCKET_NULL)
result = [self localPortFromSocket6:socket6FD];
});
return result;
}
}
- (NSString *)connectedHost4
{
if (socket4FD != SOCKET_NULL)
return [self connectedHostFromSocket4:socket4FD];
return nil;
}
- (NSString *)connectedHost6
{
if (socket6FD != SOCKET_NULL)
return [self connectedHostFromSocket6:socket6FD];
return nil;
}
- (uint16_t)connectedPort4
{
if (socket4FD != SOCKET_NULL)
return [self connectedPortFromSocket4:socket4FD];
return 0;
}
- (uint16_t)connectedPort6
{
if (socket6FD != SOCKET_NULL)
return [self connectedPortFromSocket6:socket6FD];
return 0;
}
- (NSString *)localHost4
{
if (socket4FD != SOCKET_NULL)
return [self localHostFromSocket4:socket4FD];
return nil;
}
- (NSString *)localHost6
{
if (socket6FD != SOCKET_NULL)
return [self localHostFromSocket6:socket6FD];
return nil;
}
- (uint16_t)localPort4
{
if (socket4FD != SOCKET_NULL)
return [self localPortFromSocket4:socket4FD];
return 0;
}
- (uint16_t)localPort6
{
if (socket6FD != SOCKET_NULL)
return [self localPortFromSocket6:socket6FD];
return 0;
}
- (NSString *)connectedHostFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return nil;
}
return [[self class] hostFromAddress4:&sockaddr4];
}
- (NSString *)connectedHostFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return nil;
}
return [[self class] hostFromAddress6:&sockaddr6];
}
- (uint16_t)connectedPortFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return 0;
}
return [[self class] portFromAddress4:&sockaddr4];
}
- (uint16_t)connectedPortFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return 0;
}
return [[self class] portFromAddress6:&sockaddr6];
}
- (NSString *)localHostFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return nil;
}
return [[self class] hostFromAddress4:&sockaddr4];
}
- (NSString *)localHostFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return nil;
}
return [[self class] hostFromAddress6:&sockaddr6];
}
- (uint16_t)localPortFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return 0;
}
return [[self class] portFromAddress4:&sockaddr4];
}
- (uint16_t)localPortFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return 0;
}
return [[self class] portFromAddress6:&sockaddr6];
}
- (NSData *)connectedAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
}
}
if (socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
}
}
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
return [result autorelease];
}
- (NSData *)localAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
}
}
if (socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
}
}
};
if (dispatch_get_current_queue() == socketQueue)
block();
else
dispatch_sync(socketQueue, block);
return [result autorelease];
}
- (BOOL)isIPv4
{
if (dispatch_get_current_queue() == socketQueue)
{
return (socket4FD != SOCKET_NULL);
}
else
{
__block BOOL result = NO;
dispatch_sync(socketQueue, ^{
result = (socket4FD != SOCKET_NULL);
});
return result;
}
}
- (BOOL)isIPv6
{
if (dispatch_get_current_queue() == socketQueue)
{
return (socket6FD != SOCKET_NULL);
}
else
{
__block BOOL result = NO;
dispatch_sync(socketQueue, ^{
result = (socket6FD != SOCKET_NULL);
});
return result;
}
}
- (BOOL)isSecure
{
if (dispatch_get_current_queue() == socketQueue)
{
return (flags & kSocketSecure) ? YES : NO;
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = (flags & kSocketSecure) ? YES : NO;
});
return result;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Finds the address of an interface description.
* An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
*
* The interface description may optionally contain a port number at the end, separated by a colon.
* If a non-zero port parameter is provided, any port number in the interface description is ignored.
*
* The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
**/
- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
address6:(NSMutableData **)interfaceAddr6Ptr
fromDescription:(NSString *)interfaceDescription
port:(uint16_t)port
{
NSMutableData *addr4 = nil;
NSMutableData *addr6 = nil;
NSString *interface = nil;
NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
if ([components count] > 0)
{
NSString *temp = [components objectAtIndex:0];
if ([temp length] > 0)
{
interface = temp;
}
}
if ([components count] > 1 && port == 0)
{
long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
if (portL > 0 && portL <= UINT16_MAX)
{
port = (uint16_t)portL;
}
}
if (interface == nil)
{
// ANY address
struct sockaddr_in nativeAddr4;
memset(&nativeAddr4, 0, sizeof(nativeAddr4));
nativeAddr4.sin_len = sizeof(nativeAddr4);
nativeAddr4.sin_family = AF_INET;
nativeAddr4.sin_port = htons(port);
nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in6 nativeAddr6;
memset(&nativeAddr6, 0, sizeof(nativeAddr6));
nativeAddr6.sin6_len = sizeof(nativeAddr6);
nativeAddr6.sin6_family = AF_INET6;
nativeAddr6.sin6_port = htons(port);
nativeAddr6.sin6_addr = in6addr_any;
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
{
// LOOPBACK address
struct sockaddr_in nativeAddr4;
memset(&nativeAddr4, 0, sizeof(nativeAddr4));
nativeAddr4.sin_len = sizeof(struct sockaddr_in);
nativeAddr4.sin_family = AF_INET;
nativeAddr4.sin_port = htons(port);
nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 nativeAddr6;
memset(&nativeAddr6, 0, sizeof(nativeAddr6));
nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
nativeAddr6.sin6_family = AF_INET6;
nativeAddr6.sin6_port = htons(port);
nativeAddr6.sin6_addr = in6addr_loopback;
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
const char *iface = [interface UTF8String];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
{
// IPv4
struct sockaddr_in nativeAddr4;
memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
nativeAddr4.sin_port = htons(port);
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
else
{
char ip[INET_ADDRSTRLEN];
const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
nativeAddr4.sin_port = htons(port);
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
}
}
else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
{
// IPv6
struct sockaddr_in6 nativeAddr6;
memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
nativeAddr6.sin6_port = htons(port);
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
char ip[INET6_ADDRSTRLEN];
const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
nativeAddr6.sin6_port = htons(port);
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
}
if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}
- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
{
readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(readSource, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LogVerbose(@"readEventBlock");
socketFDBytesAvailable = dispatch_source_get_data(readSource);
LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable);
if (socketFDBytesAvailable > 0)
[self doReadData];
else
[self doReadEOF];
[pool drain];
});
dispatch_source_set_event_handler(writeSource, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LogVerbose(@"writeEventBlock");
flags |= kSocketCanAcceptBytes;
[self doWriteData];
[pool drain];
});
// Setup cancel handlers
__block int socketFDRefCount = 2;
dispatch_source_t theReadSource = readSource;
dispatch_source_t theWriteSource = writeSource;
dispatch_source_set_cancel_handler(readSource, ^{
LogVerbose(@"readCancelBlock");
LogVerbose(@"dispatch_release(readSource)");
dispatch_release(theReadSource);
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socketFD)");
close(socketFD);
}
});
dispatch_source_set_cancel_handler(writeSource, ^{
LogVerbose(@"writeCancelBlock");
LogVerbose(@"dispatch_release(writeSource)");
dispatch_release(theWriteSource);
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socketFD)");
close(socketFD);
}
});
// We will not be able to read until data arrives.
// But we should be able to write immediately.
socketFDBytesAvailable = 0;
flags &= ~kReadSourceSuspended;
LogVerbose(@"dispatch_resume(readSource)");
dispatch_resume(readSource);
flags |= kSocketCanAcceptBytes;
flags |= kWriteSourceSuspended;
}
- (BOOL)usingCFStream
{
#if TARGET_OS_IPHONE
if (flags & kSocketSecure)
{
// Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS,
// we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo!
//
// Thus we're not able to use the GCD read/write sources in this particular scenario.
return YES;
}
#endif
return NO;
}
- (void)suspendReadSource
{
if (!(flags & kReadSourceSuspended))
{
LogVerbose(@"dispatch_suspend(readSource)");
dispatch_suspend(readSource);
flags |= kReadSourceSuspended;
}
}
- (void)resumeReadSource
{
if (flags & kReadSourceSuspended)
{
LogVerbose(@"dispatch_resume(readSource)");
dispatch_resume(readSource);
flags &= ~kReadSourceSuspended;
}
}
- (void)suspendWriteSource
{
if (!(flags & kWriteSourceSuspended))
{
LogVerbose(@"dispatch_suspend(writeSource)");
dispatch_suspend(writeSource);
flags |= kWriteSourceSuspended;
}
}
- (void)resumeWriteSource
{
if (flags & kWriteSourceSuspended)
{
LogVerbose(@"dispatch_resume(writeSource)");
dispatch_resume(writeSource);
flags &= ~kWriteSourceSuspended;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Reading
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
{
[self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
[self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
}
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag
{
if (offset > [buffer length]) return;
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:length
timeout:timeout
readLength:0
terminator:nil
tag:tag];
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
[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
{
[self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
}
- (void)readDataToLength:(NSUInteger)length
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
if (length == 0) return;
if (offset > [buffer length]) return;
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:0
timeout:timeout
readLength:length
terminator:nil
tag:tag];
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
[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
{
[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
[self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
}
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
{
[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
}
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag
{
if ([data length] == 0) return;
if (offset > [buffer length]) return;
if (length > 0 && length < [data length]) return;
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:length
timeout:timeout
readLength:0
terminator:data
tag:tag];
dispatch_async(socketQueue, ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
[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];
}
/**
* This method starts a new read, if needed.
*
* It is called when:
* - a user requests a read
* - after a read request has finished (to handle the next request)
* - immediately after the socket opens to handle any pending requests
*
* This method also handles auto-disconnect post read/write completion.
**/
- (void)maybeDequeueRead
{
LogTrace();
NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
// If we're not currently processing a read AND we have an available read stream
if ((currentRead == nil) && (flags & kConnected))
{
if ([readQueue count] > 0)
{
// Dequeue the next object in the write queue
currentRead = [[readQueue objectAtIndex:0] retain];
[readQueue removeObjectAtIndex:0];
if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
{
LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
// Attempt to start TLS
flags |= kStartingReadTLS;
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
[self maybeStartTLS];
}
else
{
LogVerbose(@"Dequeued GCDAsyncReadPacket");
// Setup read timer (if needed)
[self setupReadTimerWithTimeout:currentRead->timeout];
// Immediately read, if possible
[self doReadData];
}
}
else if (flags & kDisconnectAfterReads)
{
if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
[self closeWithError:nil];
}
}
else
{
[self closeWithError:nil];
}
}
else if (flags & kSocketSecure)
{
[self flushSSLBuffers];
// Edge case:
//
// We just drained all data from the ssl buffers,
// and all known data from the socket (socketFDBytesAvailable).
//
// If we didn't get any data from this process,
// then we may have reached the end of the TCP stream.
//
// Be sure callbacks are enabled so we're notified about a disconnection.
if ([partialReadBuffer length] == 0)
{
if ([self usingCFStream]) {
// Callbacks never disabled
}
else {
[self resumeReadSource];
}
}
}
}
}
- (void)flushSSLBuffers
{
LogTrace();
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
if ([partialReadBuffer length] > 0)
{
// Only flush the ssl buffers if the partialReadBuffer is empty.
// This is to avoid growing the partialReadBuffer inifinitely large.
return;
}
#if TARGET_OS_IPHONE
if (flags & kSecureSocketHasBytesAvailable)
{
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
{
[partialReadBuffer setLength:(partialReadBufferOffset + result)];
}
flags &= ~kSecureSocketHasBytesAvailable;
}
#else
__block NSUInteger estimatedBytesAvailable;
dispatch_block_t updateEstimatedBytesAvailable = ^{
// Figure out if there is any data available to be read
//
// socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
// [sslReadBuffer length] <- Number of encrypted bytes we've buffered from bsd socket
// sslInternalBufSize <- Number od 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.
// However, we do know this is an upper bound on the estimation.
estimatedBytesAvailable = socketFDBytesAvailable + [sslReadBuffer length];
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
estimatedBytesAvailable += sslInternalBufSize;
};
updateEstimatedBytesAvailable();
if (estimatedBytesAvailable > 0)
{
LogVerbose(@"%@ - Flushing ssl buffers into partialReadBuffer...", THIS_METHOD);
BOOL done = NO;
do
{
LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
// Make room in the partialReadBuffer
NSUInteger partialReadBufferOffset = [partialReadBuffer length];
[partialReadBuffer increaseLengthBy:estimatedBytesAvailable];
uint8_t *buffer = (uint8_t *)[partialReadBuffer mutableBytes] + partialReadBufferOffset;
size_t bytesRead = 0;
// Read data into partialReadBuffer
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 (result != noErr)
{
done = YES;
}
else
{
updateEstimatedBytesAvailable();
}
} while (!done && estimatedBytesAvailable > 0);
}
#endif
}
- (void)doReadData
{
LogTrace();
// This method is called on the socketQueue.
// It might be called directly, or via the readSource when data is available to be read.
if ((currentRead == nil) || (flags & kReadsPaused))
{
LogVerbose(@"No currentRead or kReadsPaused");
// Unable to read at this time
if (flags & kSocketSecure)
{
// Here's the situation:
//
// We have an established secure connection.
// There may not be a currentRead, but there might be encrypted data sitting around for us.
// When the user does get around to issuing a read, that encrypted data will need to be decrypted.
//
// So why make the user wait?
// We might as well get a head start on decrypting some data now.
//
// The other reason we do this has to do with detecting a socket disconnection.
// The SSL/TLS protocol has it's own disconnection handshake.
// So when a secure socket is closed, a "goodbye" packet comes across the wire.
// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
[self flushSSLBuffers];
}
if ([self usingCFStream])
{
// CFReadStream only fires once when there is available data.
// It won't fire again until we've invoked CFReadStreamRead.
}
else
{
// If the readSource is firing, we need to pause it
// or else it will continue to fire over and over again.
//
// If the readSource is not firing,
// we want it to continue monitoring the socket.
if (socketFDBytesAvailable > 0)
{
[self suspendReadSource];
}
}
return;
}
BOOL hasBytesAvailable;
unsigned long estimatedBytesAvailable;
#if TARGET_OS_IPHONE
if (flags & kSocketSecure)
{
// Relegated to using CFStream... :( Boo! Give us SecureTransport Apple!
estimatedBytesAvailable = 0;
hasBytesAvailable = (flags & kSecureSocketHasBytesAvailable) ? YES : NO;
}
else
{
estimatedBytesAvailable = socketFDBytesAvailable;
hasBytesAvailable = (estimatedBytesAvailable > 0);
}
#else
estimatedBytesAvailable = socketFDBytesAvailable;
if (flags & kSocketSecure)
{
// There are 2 buffers to be aware of here.
//
// We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
// We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
// Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
// SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
//
// The first buffer is one we create.
// SecureTransport often requests small amounts of data.
// This has to do with the encypted packets that are coming across the TCP stream.
// But it's non-optimal to do a bunch of small reads from the BSD socket.
// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
// and may store excess in the sslReadBuffer.
estimatedBytesAvailable += [sslReadBuffer length];
// The second buffer is within SecureTransport.
// As mentioned earlier, there are encrypted packets coming across the TCP stream.
// SecureTransport needs the entire packet to decrypt it.
// But if the entire packet produces X bytes of decrypted data,
// and we only asked SecureTransport for X/2 bytes of data,
// it must store the extra X/2 bytes of decrypted data for the next read.
//
// The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
// From the documentation:
//
// "This function does not block or cause any low-level read operations to occur."
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);