/io/libraries/CocoaAsyncSocket/AsyncSocket.m
Objective C | 1632 lines | 1091 code | 288 blank | 253 comment | 238 complexity | e02a0d7d31b5ca43593281396cf8de94 MD5 | raw file
- //
- // AsyncSocket.m
- //
- // This class is in the public domain.
- // Originally created by Dustin Voss on Wed Jan 29 2003.
- // Updated and maintained by Deusty Designs and the Mac development community.
- //
- // http://code.google.com/p/cocoaasyncsocket/
- //
- #if ! __has_feature(objc_arc)
- #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
- #endif
- #import "AsyncSocket.h"
- #import <sys/socket.h>
- #import <netinet/in.h>
- #import <arpa/inet.h>
- #import <netdb.h>
- #if TARGET_OS_IPHONE
- // Note: You may need to add the CFNetwork Framework to your project
- #import <CFNetwork/CFNetwork.h>
- #endif
- #pragma mark Declarations
- #define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default
- #define READQUEUE_CAPACITY 5 // Initial capacity
- #define WRITEQUEUE_CAPACITY 5 // Initial capacity
- #define READALL_CHUNKSIZE 256 // Incremental increase in buffer size
- #define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass
- // AsyncSocket is RunLoop based, and is thus not thread-safe.
- // You must always access your AsyncSocket instance from the thread/runloop in which the instance is running.
- // You can use methods such as performSelectorOnThread to accomplish this.
- // Failure to comply with these thread-safety rules may result in errors.
- // You can enable this option to help diagnose where you are incorrectly accessing your socket.
- #if DEBUG
- #define DEBUG_THREAD_SAFETY 1
- #else
- #define DEBUG_THREAD_SAFETY 0
- #endif
- //
- // If you constantly need to access your socket from multiple threads
- // then you may consider using GCDAsyncSocket instead, which is thread-safe.
- NSString *const AsyncSocketException = @"AsyncSocketException";
- NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain";
- enum AsyncSocketFlags
- {
- kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled
- kDidStartDelegate = 1 << 1, // If set, disconnection results in delegate call
- kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream
- kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream
- kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete
- kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete
- kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed
- kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued
- kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued
- kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error
- kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled
- kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled
- kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown.
- kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown.
- };
- @interface AsyncSocket (Private)
- // Connecting
- - (void)startConnectTimeout:(NSTimeInterval)timeout;
- - (void)endConnectTimeout;
- - (void)doConnectTimeout:(NSTimer *)timer;
- // Socket Implementation
- - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr;
- - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
- - (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr;
- - (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr;
- - (BOOL)configureSocketAndReturnError:(NSError **)errPtr;
- - (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
- - (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket;
- - (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err;
- // Stream Implementation
- - (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr;
- - (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr;
- - (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr;
- - (BOOL)configureStreamsAndReturnError:(NSError **)errPtr;
- - (BOOL)openStreamsAndReturnError:(NSError **)errPtr;
- - (void)doStreamOpen;
- - (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr;
- // Disconnect Implementation
- - (void)closeWithError:(NSError *)err;
- - (void)recoverUnreadData;
- - (void)emptyQueues;
- - (void)close;
- // Errors
- - (NSError *)getErrnoError;
- - (NSError *)getAbortError;
- - (NSError *)getStreamError;
- - (NSError *)getSocketError;
- - (NSError *)getConnectTimeoutError;
- - (NSError *)getReadMaxedOutError;
- - (NSError *)getReadTimeoutError;
- - (NSError *)getWriteTimeoutError;
- - (NSError *)errorFromCFStreamError:(CFStreamError)err;
- // Diagnostics
- - (BOOL)isDisconnected;
- - (BOOL)areStreamsConnected;
- - (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket;
- - (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket;
- - (NSString *)connectedHostFromCFSocket4:(CFSocketRef)socket;
- - (NSString *)connectedHostFromCFSocket6:(CFSocketRef)socket;
- - (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket;
- - (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket;
- - (UInt16)connectedPortFromCFSocket4:(CFSocketRef)socket;
- - (UInt16)connectedPortFromCFSocket6:(CFSocketRef)socket;
- - (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket;
- - (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket;
- - (NSString *)localHostFromCFSocket4:(CFSocketRef)socket;
- - (NSString *)localHostFromCFSocket6:(CFSocketRef)socket;
- - (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket;
- - (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket;
- - (UInt16)localPortFromCFSocket4:(CFSocketRef)socket;
- - (UInt16)localPortFromCFSocket6:(CFSocketRef)socket;
- - (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4;
- - (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6;
- - (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4;
- - (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6;
- // Reading
- - (void)doBytesAvailable;
- - (void)completeCurrentRead;
- - (void)endCurrentRead;
- - (void)scheduleDequeueRead;
- - (void)maybeDequeueRead;
- - (void)doReadTimeout:(NSTimer *)timer;
- // Writing
- - (void)doSendBytes;
- - (void)completeCurrentWrite;
- - (void)endCurrentWrite;
- - (void)scheduleDequeueWrite;
- - (void)maybeDequeueWrite;
- - (void)maybeScheduleDisconnect;
- - (void)doWriteTimeout:(NSTimer *)timer;
- // Run Loop
- - (void)runLoopAddSource:(CFRunLoopSourceRef)source;
- - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source;
- - (void)runLoopAddTimer:(NSTimer *)timer;
- - (void)runLoopRemoveTimer:(NSTimer *)timer;
- - (void)runLoopUnscheduleReadStream;
- - (void)runLoopUnscheduleWriteStream;
- // Security
- - (void)maybeStartTLS;
- - (void)onTLSHandshakeSuccessful;
- // Callbacks
- - (void)doCFCallback:(CFSocketCallBackType)type
- forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData;
- - (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream;
- - (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream;
- @end
- static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *);
- static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo);
- static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo);
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * The AsyncReadPacket encompasses the instructions for any given read.
- * The content of a read packet allows the code to determine if we're:
- * - reading to a certain length
- * - reading to a certain separator
- * - or simply reading the first chunk of available data
- **/
- @interface AsyncReadPacket : NSObject
- {
- @public
- NSMutableData *buffer;
- NSUInteger startOffset;
- NSUInteger bytesDone;
- NSUInteger maxLength;
- NSTimeInterval timeout;
- NSUInteger readLength;
- NSData *term;
- BOOL bufferOwner;
- NSUInteger originalBufferLength;
- long tag;
- }
- - (id)initWithData:(NSMutableData *)d
- startOffset:(NSUInteger)s
- maxLength:(NSUInteger)m
- timeout:(NSTimeInterval)t
- readLength:(NSUInteger)l
- terminator:(NSData *)e
- tag:(long)i;
- - (NSUInteger)readLengthForNonTerm;
- - (NSUInteger)readLengthForTerm;
- - (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr;
- - (NSUInteger)prebufferReadLengthForTerm;
- - (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes;
- @end
- @implementation AsyncReadPacket
- - (id)initWithData:(NSMutableData *)d
- startOffset:(NSUInteger)s
- maxLength:(NSUInteger)m
- timeout:(NSTimeInterval)t
- readLength:(NSUInteger)l
- terminator:(NSData *)e
- tag:(long)i
- {
- if((self = [super init]))
- {
- if (d)
- {
- buffer = d;
- startOffset = s;
- bufferOwner = NO;
- originalBufferLength = [d length];
- }
- else
- {
- if (readLength > 0)
- buffer = [[NSMutableData alloc] initWithLength:readLength];
- else
- buffer = [[NSMutableData alloc] initWithLength:0];
-
- startOffset = 0;
- bufferOwner = YES;
- originalBufferLength = 0;
- }
-
- bytesDone = 0;
- maxLength = m;
- timeout = t;
- readLength = l;
- term = [e copy];
- tag = i;
- }
- return self;
- }
- /**
- * For read packets without a set terminator, returns the safe length of data that can be read
- * without exceeding the maxLength, or forcing a resize of the buffer if at all possible.
- **/
- - (NSUInteger)readLengthForNonTerm
- {
- NSAssert(term == nil, @"This method does not apply to term reads");
-
- if (readLength > 0)
- {
- // Read a specific length of data
-
- return readLength - bytesDone;
-
- // No need to avoid resizing the buffer.
- // It should be resized if the buffer space is less than the requested read length.
- }
- else
- {
- // Read all available data
-
- NSUInteger result = READALL_CHUNKSIZE;
-
- if (maxLength > 0)
- {
- result = MIN(result, (maxLength - bytesDone));
- }
-
- if (!bufferOwner)
- {
- // We did NOT create the buffer.
- // It is owned by the caller.
- // Avoid resizing the buffer if at all possible.
-
- if ([buffer length] == originalBufferLength)
- {
- NSUInteger buffSize = [buffer length];
- NSUInteger buffSpace = buffSize - startOffset - bytesDone;
-
- if (buffSpace > 0)
- {
- result = MIN(result, buffSpace);
- }
- }
- }
-
- return result;
- }
- }
- /**
- * For read packets with a set terminator, returns the safe length of data that can be read
- * without going over a terminator, or the maxLength, or forcing a resize of the buffer if at all possible.
- *
- * It is assumed the terminator has not already been read.
- **/
- - (NSUInteger)readLengthForTerm
- {
- NSAssert(term != nil, @"This method does not apply to non-term reads");
-
- // What we're going to do is look for a partial sequence of the terminator at the end of the buffer.
- // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term,
- // and we can only read that amount.
- // Otherwise, we're safe to read the entire length of the term.
-
- NSUInteger termLength = [term length];
-
- // Shortcuts
- if (bytesDone == 0) return termLength;
- if (termLength == 1) return termLength;
-
- // i = index within buffer at which to check data
- // j = length of term to check against
-
- NSUInteger i, j;
- if (bytesDone >= termLength)
- {
- i = bytesDone - termLength + 1;
- j = termLength - 1;
- }
- else
- {
- i = 0;
- j = bytesDone;
- }
-
- NSUInteger result = termLength;
-
- void *buf = [buffer mutableBytes];
- const void *termBuf = [term bytes];
-
- while (i < bytesDone)
- {
- void *subbuf = buf + startOffset + i;
-
- if (memcmp(subbuf, termBuf, j) == 0)
- {
- result = termLength - j;
- break;
- }
-
- i++;
- j--;
- }
-
- if (maxLength > 0)
- {
- result = MIN(result, (maxLength - bytesDone));
- }
-
- if (!bufferOwner)
- {
- // We did NOT create the buffer.
- // It is owned by the caller.
- // Avoid resizing the buffer if at all possible.
-
- if ([buffer length] == originalBufferLength)
- {
- NSUInteger buffSize = [buffer length];
- NSUInteger buffSpace = buffSize - startOffset - bytesDone;
-
- if (buffSpace > 0)
- {
- result = MIN(result, buffSpace);
- }
- }
- }
-
- return result;
- }
- /**
- * For read packets with a set terminator,
- * returns the safe length of data that can be read from the given preBuffer,
- * without going over a terminator or the maxLength.
- *
- * It is assumed the terminator has not already been read.
- **/
- - (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr
- {
- NSAssert(term != nil, @"This method does not apply to non-term reads");
- NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!");
-
- // We know that the terminator, as a whole, doesn't exist in our own buffer.
- // But it is possible that a portion of it exists in our buffer.
- // So we're going to look for the terminator starting with a portion of our own buffer.
- //
- // Example:
- //
- // term length = 3 bytes
- // bytesDone = 5 bytes
- // preBuffer length = 5 bytes
- //
- // If we append the preBuffer to our buffer,
- // it would look like this:
- //
- // ---------------------
- // |B|B|B|B|B|P|P|P|P|P|
- // ---------------------
- //
- // So we start our search here:
- //
- // ---------------------
- // |B|B|B|B|B|P|P|P|P|P|
- // -------^-^-^---------
- //
- // And move forwards...
- //
- // ---------------------
- // |B|B|B|B|B|P|P|P|P|P|
- // ---------^-^-^-------
- //
- // Until we find the terminator or reach the end.
- //
- // ---------------------
- // |B|B|B|B|B|P|P|P|P|P|
- // ---------------^-^-^-
-
- BOOL found = NO;
-
- NSUInteger termLength = [term length];
- NSUInteger preBufferLength = [preBuffer length];
-
- if ((bytesDone + preBufferLength) < termLength)
- {
- // Not enough data for a full term sequence yet
- return preBufferLength;
- }
-
- NSUInteger maxPreBufferLength;
- if (maxLength > 0) {
- maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
-
- // Note: maxLength >= termLength
- }
- else {
- maxPreBufferLength = preBufferLength;
- }
-
- Byte seq[termLength];
- const void *termBuf = [term bytes];
-
- NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
- void *buf = [buffer mutableBytes] + startOffset + bytesDone - bufLen;
-
- NSUInteger preLen = termLength - bufLen;
- void *pre = (void *)[preBuffer bytes];
-
- NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
-
- NSUInteger result = preBufferLength;
-
- NSUInteger i;
- for (i = 0; i < loopCount; i++)
- {
- if (bufLen > 0)
- {
- // Combining bytes from buffer and preBuffer
-
- memcpy(seq, buf, bufLen);
- memcpy(seq + bufLen, pre, preLen);
-
- if (memcmp(seq, termBuf, termLength) == 0)
- {
- result = preLen;
- found = YES;
- break;
- }
-
- buf++;
- bufLen--;
- preLen++;
- }
- else
- {
- // Comparing directly from preBuffer
-
- if (memcmp(pre, termBuf, termLength) == 0)
- {
- NSUInteger preOffset = pre - [preBuffer bytes]; // pointer arithmetic
-
- result = preOffset + termLength;
- found = YES;
- break;
- }
-
- pre++;
- }
- }
-
- // There is no need to avoid resizing the buffer in this particular situation.
-
- if (foundPtr) *foundPtr = found;
- return result;
- }
- /**
- * Assuming pre-buffering is enabled, returns the amount of data that can be read
- * without going over the maxLength.
- **/
- - (NSUInteger)prebufferReadLengthForTerm
- {
- NSAssert(term != nil, @"This method does not apply to non-term reads");
-
- NSUInteger result = READALL_CHUNKSIZE;
-
- if (maxLength > 0)
- {
- result = MIN(result, (maxLength - bytesDone));
- }
-
- if (!bufferOwner)
- {
- // We did NOT create the buffer.
- // It is owned by the caller.
- // Avoid resizing the buffer if at all possible.
-
- if ([buffer length] == originalBufferLength)
- {
- NSUInteger buffSize = [buffer length];
- NSUInteger buffSpace = buffSize - startOffset - bytesDone;
-
- if (buffSpace > 0)
- {
- result = MIN(result, buffSpace);
- }
- }
- }
-
- return result;
- }
- /**
- * For read packets with a set terminator, scans the packet buffer for the term.
- * It is assumed the terminator had not been fully read prior to the new bytes.
- *
- * If the term is found, the number of excess bytes after the term are returned.
- * If the term is not found, this method will return -1.
- *
- * Note: A return value of zero means the term was found at the very end.
- **/
- - (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes
- {
- NSAssert(term != nil, @"This method does not apply to non-term reads");
- NSAssert(bytesDone >= numBytes, @"Invoked with invalid numBytes!");
-
- // We try to start the search such that the first new byte read matches up with the last byte of the term.
- // We continue searching forward after this until the term no longer fits into the buffer.
-
- NSUInteger termLength = [term length];
- const void *termBuffer = [term bytes];
-
- // Remember: This method is called after the bytesDone variable has been updated.
-
- NSUInteger prevBytesDone = bytesDone - numBytes;
-
- NSUInteger i;
- if (prevBytesDone >= termLength)
- i = prevBytesDone - termLength + 1;
- else
- i = 0;
-
- while ((i + termLength) <= bytesDone)
- {
- void *subBuffer = [buffer mutableBytes] + startOffset + i;
-
- if(memcmp(subBuffer, termBuffer, termLength) == 0)
- {
- return bytesDone - (i + termLength);
- }
-
- i++;
- }
-
- return -1;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * The AsyncWritePacket encompasses the instructions for any given write.
- **/
- @interface AsyncWritePacket : NSObject
- {
- @public
- NSData *buffer;
- NSUInteger bytesDone;
- long tag;
- NSTimeInterval timeout;
- }
- - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
- @end
- @implementation AsyncWritePacket
- - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
- {
- if((self = [super init]))
- {
- buffer = d;
- timeout = t;
- tag = i;
- bytesDone = 0;
- }
- return self;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
- * This class my be altered to support more than just TLS in the future.
- **/
- @interface AsyncSpecialPacket : NSObject
- {
- @public
- NSDictionary *tlsSettings;
- }
- - (id)initWithTLSSettings:(NSDictionary *)settings;
- @end
- @implementation AsyncSpecialPacket
- - (id)initWithTLSSettings:(NSDictionary *)settings
- {
- if((self = [super init]))
- {
- tlsSettings = [settings copy];
- }
- return self;
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation AsyncSocket
- - (id)init
- {
- return [self initWithDelegate:nil userData:0];
- }
- - (id)initWithDelegate:(id)delegate
- {
- return [self initWithDelegate:delegate userData:0];
- }
- // Designated initializer.
- - (id)initWithDelegate:(id)delegate userData:(long)userData
- {
- if((self = [super init]))
- {
- theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0;
- theDelegate = delegate;
- theUserData = userData;
-
- theNativeSocket4 = 0;
- theNativeSocket6 = 0;
-
- theSocket4 = NULL;
- theSource4 = NULL;
-
- theSocket6 = NULL;
- theSource6 = NULL;
-
- theRunLoop = NULL;
- theReadStream = NULL;
- theWriteStream = NULL;
-
- theConnectTimer = nil;
-
- theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY];
- theCurrentRead = nil;
- theReadTimer = nil;
-
- partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE];
-
- theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY];
- theCurrentWrite = nil;
- theWriteTimer = nil;
-
- // Socket context
- NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext");
- theContext.version = 0;
- theContext.info = (__bridge void *)(self);
- theContext.retain = nil;
- theContext.release = nil;
- theContext.copyDescription = nil;
-
- // Default run loop modes
- theRunLoopModes = [NSArray arrayWithObject:NSDefaultRunLoopMode];
- }
- return self;
- }
- // The socket may been initialized in a connected state and auto-released, so this should close it down cleanly.
- - (void)dealloc
- {
- [self close];
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Thread-Safety
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)checkForThreadSafety
- {
- if (theRunLoop && (theRunLoop != CFRunLoopGetCurrent()))
- {
- // AsyncSocket is RunLoop based.
- // It is designed to be run and accessed from a particular thread/runloop.
- // As such, it is faster as it does not have the overhead of locks/synchronization.
- //
- // However, this places a minimal requirement on the developer to maintain thread-safety.
- // If you are seeing errors or crashes in AsyncSocket,
- // it is very likely that thread-safety has been broken.
- // This method may be enabled via the DEBUG_THREAD_SAFETY macro,
- // and will allow you to discover the place in your code where thread-safety is being broken.
- //
- // Note:
- //
- // If you find you constantly need to access your socket from various threads,
- // you may prefer to use GCDAsyncSocket which is thread-safe.
-
- [NSException raise:AsyncSocketException
- format:@"Attempting to access AsyncSocket instance from incorrect thread."];
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Accessors
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (long)userData
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return theUserData;
- }
- - (void)setUserData:(long)userData
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- theUserData = userData;
- }
- - (id)delegate
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return theDelegate;
- }
- - (void)setDelegate:(id)delegate
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- theDelegate = delegate;
- }
- - (BOOL)canSafelySetDelegate
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil);
- }
- - (CFSocketRef)getCFSocket
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- if(theSocket4)
- return theSocket4;
- else
- return theSocket6;
- }
- - (CFReadStreamRef)getCFReadStream
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return theReadStream;
- }
- - (CFWriteStreamRef)getCFWriteStream
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return theWriteStream;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Progress
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- // Check to make sure we're actually reading something right now,
- // and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS).
- if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]])
- {
- if (tag != NULL) *tag = 0;
- if (done != NULL) *done = 0;
- if (total != NULL) *total = 0;
-
- return NAN;
- }
-
- // It's only possible to know the progress of our read if we're reading to a certain length.
- // If we're reading to data, we of course have no idea when the data will arrive.
- // If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
-
- NSUInteger d = theCurrentRead->bytesDone;
- NSUInteger t = theCurrentRead->readLength;
-
- if (tag != NULL) *tag = theCurrentRead->tag;
- if (done != NULL) *done = d;
- if (total != NULL) *total = t;
-
- if (t > 0.0)
- return (float)d / (float)t;
- else
- return 1.0F;
- }
- - (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- // Check to make sure we're actually writing something right now,
- // and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS).
- if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]])
- {
- if (tag != NULL) *tag = 0;
- if (done != NULL) *done = 0;
- if (total != NULL) *total = 0;
-
- return NAN;
- }
-
- NSUInteger d = theCurrentWrite->bytesDone;
- NSUInteger t = [theCurrentWrite->buffer length];
-
- if (tag != NULL) *tag = theCurrentWrite->tag;
- if (done != NULL) *done = d;
- if (total != NULL) *total = t;
-
- return (float)d / (float)t;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Run Loop
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)runLoopAddSource:(CFRunLoopSourceRef)source
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode);
- }
- }
- - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode);
- }
- }
- - (void)runLoopAddSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode
- {
- CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode);
- }
- - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode
- {
- CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode);
- }
- - (void)runLoopAddTimer:(NSTimer *)timer
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode);
- }
- }
- - (void)runLoopRemoveTimer:(NSTimer *)timer
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode);
- }
- }
- - (void)runLoopAddTimer:(NSTimer *)timer mode:(NSString *)runLoopMode
- {
- CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode);
- }
- - (void)runLoopRemoveTimer:(NSTimer *)timer mode:(NSString *)runLoopMode
- {
- CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode);
- }
- - (void)runLoopUnscheduleReadStream
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, (__bridge CFStringRef)runLoopMode);
- }
- CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
- }
- - (void)runLoopUnscheduleWriteStream
- {
- for (NSString *runLoopMode in theRunLoopModes)
- {
- CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, (__bridge CFStringRef)runLoopMode);
- }
- CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Configuration
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * See the header file for a full explanation of pre-buffering.
- **/
- - (void)enablePreBuffering
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- theFlags |= kEnablePreBuffering;
- }
- /**
- * See the header file for a full explanation of this method.
- **/
- - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"moveToRunLoop must be called from within the current RunLoop!");
-
- if(runLoop == nil)
- {
- return NO;
- }
- if(theRunLoop == [runLoop getCFRunLoop])
- {
- return YES;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueReadScheduled;
- theFlags &= ~kDequeueWriteScheduled;
-
- if(theReadStream && theWriteStream)
- {
- [self runLoopUnscheduleReadStream];
- [self runLoopUnscheduleWriteStream];
- }
-
- if(theSource4) [self runLoopRemoveSource:theSource4];
- if(theSource6) [self runLoopRemoveSource:theSource6];
-
- if(theReadTimer) [self runLoopRemoveTimer:theReadTimer];
- if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer];
-
- theRunLoop = [runLoop getCFRunLoop];
-
- if(theReadTimer) [self runLoopAddTimer:theReadTimer];
- if(theWriteTimer) [self runLoopAddTimer:theWriteTimer];
-
- if(theSource4) [self runLoopAddSource:theSource4];
- if(theSource6) [self runLoopAddSource:theSource6];
-
- if(theReadStream && theWriteStream)
- {
- if(![self attachStreamsToRunLoop:runLoop error:nil])
- {
- return NO;
- }
- }
-
- [runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes];
- [runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes];
- [runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes];
-
- return YES;
- }
- /**
- * See the header file for a full explanation of this method.
- **/
- - (BOOL)setRunLoopModes:(NSArray *)runLoopModes
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"setRunLoopModes must be called from within the current RunLoop!");
-
- if([runLoopModes count] == 0)
- {
- return NO;
- }
- if([theRunLoopModes isEqualToArray:runLoopModes])
- {
- return YES;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueReadScheduled;
- theFlags &= ~kDequeueWriteScheduled;
-
- if(theReadStream && theWriteStream)
- {
- [self runLoopUnscheduleReadStream];
- [self runLoopUnscheduleWriteStream];
- }
-
- if(theSource4) [self runLoopRemoveSource:theSource4];
- if(theSource6) [self runLoopRemoveSource:theSource6];
-
- if(theReadTimer) [self runLoopRemoveTimer:theReadTimer];
- if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer];
-
- theRunLoopModes = [runLoopModes copy];
-
- if(theReadTimer) [self runLoopAddTimer:theReadTimer];
- if(theWriteTimer) [self runLoopAddTimer:theWriteTimer];
-
- if(theSource4) [self runLoopAddSource:theSource4];
- if(theSource6) [self runLoopAddSource:theSource6];
-
- if(theReadStream && theWriteStream)
- {
- // Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop.
- // So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter.
- // Instead we pass nil, which will result in the method properly using the current run loop.
-
- if(![self attachStreamsToRunLoop:nil error:nil])
- {
- return NO;
- }
- }
-
- [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes];
-
- return YES;
- }
- - (BOOL)addRunLoopMode:(NSString *)runLoopMode
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"addRunLoopMode must be called from within the current RunLoop!");
-
- if(runLoopMode == nil)
- {
- return NO;
- }
- if([theRunLoopModes containsObject:runLoopMode])
- {
- return YES;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueReadScheduled;
- theFlags &= ~kDequeueWriteScheduled;
-
- NSArray *newRunLoopModes = [theRunLoopModes arrayByAddingObject:runLoopMode];
- theRunLoopModes = newRunLoopModes;
-
- if(theReadTimer) [self runLoopAddTimer:theReadTimer mode:runLoopMode];
- if(theWriteTimer) [self runLoopAddTimer:theWriteTimer mode:runLoopMode];
-
- if(theSource4) [self runLoopAddSource:theSource4 mode:runLoopMode];
- if(theSource6) [self runLoopAddSource:theSource6 mode:runLoopMode];
-
- if(theReadStream && theWriteStream)
- {
- CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode);
- CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode);
- }
-
- [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes];
-
- return YES;
- }
- - (BOOL)removeRunLoopMode:(NSString *)runLoopMode
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"addRunLoopMode must be called from within the current RunLoop!");
-
- if(runLoopMode == nil)
- {
- return NO;
- }
- if(![theRunLoopModes containsObject:runLoopMode])
- {
- return YES;
- }
-
- NSMutableArray *newRunLoopModes = [theRunLoopModes mutableCopy];
- [newRunLoopModes removeObject:runLoopMode];
-
- if([newRunLoopModes count] == 0)
- {
- return NO;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueReadScheduled;
- theFlags &= ~kDequeueWriteScheduled;
-
- theRunLoopModes = [newRunLoopModes copy];
-
- if(theReadTimer) [self runLoopRemoveTimer:theReadTimer mode:runLoopMode];
- if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer mode:runLoopMode];
-
- if(theSource4) [self runLoopRemoveSource:theSource4 mode:runLoopMode];
- if(theSource6) [self runLoopRemoveSource:theSource6 mode:runLoopMode];
-
- if(theReadStream && theWriteStream)
- {
- CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode);
- CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode);
- }
-
- [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes];
-
- return YES;
- }
- - (NSArray *)runLoopModes
- {
- #if DEBUG_THREAD_SAFETY
- [self checkForThreadSafety];
- #endif
-
- return theRunLoopModes;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Accepting
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr
- {
- return [self acceptOnInterface:nil port:port error:errPtr];
- }
-
- /**
- * To accept on a certain interface, pass the address to accept on.
- * To accept on any interface, pass nil or an empty string.
- * To accept only connections from localhost pass "localhost" or "loopback".
- **/
- - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr
- {
- if (theDelegate == NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to accept without a delegate. Set a delegate first."];
- }
-
- if (![self isDisconnected])
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to accept while connected or accepting connections. Disconnect first."];
- }
-
- // Clear queues (spurious read/write requests post disconnect)
- [self emptyQueues];
- // Set up the listen sockaddr structs if needed.
-
- NSData *address4 = nil, *address6 = nil;
- if(interface == nil || ([interface length] == 0))
- {
- // Accept on ANY address
- struct sockaddr_in nativeAddr4;
- nativeAddr4.sin_len = sizeof(struct sockaddr_in);
- nativeAddr4.sin_family = AF_INET;
- nativeAddr4.sin_port = htons(port);
- nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
- memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
-
- struct sockaddr_in6 nativeAddr6;
- nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
- nativeAddr6.sin6_family = AF_INET6;
- nativeAddr6.sin6_port = htons(port);
- nativeAddr6.sin6_flowinfo = 0;
- nativeAddr6.sin6_addr = in6addr_any;
- nativeAddr6.sin6_scope_id = 0;
-
- // Wrap the native address structures for CFSocketSetAddress.
- address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
- address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
- }
- else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
- {
- // Accept only on LOOPBACK address
- struct sockaddr_in nativeAddr4;
- nativeAddr4.sin_len = sizeof(struct sockaddr_in);
- nativeAddr4.sin_family = AF_INET;
- nativeAddr4.sin_port = htons(port);
- nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
-
- struct sockaddr_in6 nativeAddr6;
- nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
- nativeAddr6.sin6_family = AF_INET6;
- nativeAddr6.sin6_port = htons(port);
- nativeAddr6.sin6_flowinfo = 0;
- nativeAddr6.sin6_addr = in6addr_loopback;
- nativeAddr6.sin6_scope_id = 0;
-
- // Wrap the native address structures for CFSocketSetAddress.
- address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
- address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
- }
- else
- {
- NSString *portStr = [NSString stringWithFormat:@"%hu", port];
- struct addrinfo hints, *res, *res0;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
- hints.ai_flags = AI_PASSIVE;
-
- int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0);
-
- if (error)
- {
- if (errPtr)
- {
- NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
- }
- }
- else
- {
- for (res = res0; res; res = res->ai_next)
- {
- if (!address4 && (res->ai_family == AF_INET))
- {
- // Found IPv4 address
- // Wrap the native address structures for CFSocketSetAddress.
- address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- else if (!address6 && (res->ai_family == AF_INET6))
- {
- // Found IPv6 address
- // Wrap the native address structures for CFSocketSetAddress.
- address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- }
- freeaddrinfo(res0);
- }
-
- if(!address4 && !address6) return NO;
- }
- // Create the sockets.
- if (address4)
- {
- theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr];
- if (theSocket4 == NULL) goto Failed;
- }
-
- if (address6)
- {
- theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr];
-
- // Note: The iPhone doesn't currently support IPv6
-
- #if !TARGET_OS_IPHONE
- if (theSocket6 == NULL) goto Failed;
- #endif
- }
-
- // Attach the sockets to the run loop so that callback methods work
-
- [self attachSocketsToRunLoop:nil error:nil];
-
- // Set the SO_REUSEADDR flags.
- int reuseOn = 1;
- if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
- if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
- // Set the local bindings which causes the sockets to start listening.
- CFSocketError err;
- if (theSocket4)
- {
- err = CFSocketSetAddress(theSocket4, (__bridge CFDataRef)address4);
- if (err != kCFSocketSuccess) goto Failed;
-
- //NSLog(@"theSocket4: %hu", [self localPortFromCFSocket4:theSocket4]);
- }
-
- if(port == 0 && theSocket4 && theSocket6)
- {
- // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them
- // However, the kernel will choose a different port for both theSocket4 and theSocket6
- // So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6
- UInt16 chosenPort = [self localPortFromCFSocket4:theSocket4];
-
- struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes];
- if (pSockAddr6) // If statement to quiet the static analyzer
- {
- pSockAddr6->sin6_port = htons(chosenPort);
- }
- }
-
- if (theSocket6)
- {
- err = CFSocketSetAddress(theSocket6, (__bridge CFDataRef)address6);
- if (err != kCFSocketSuccess) goto Failed;
-
- //NSLog(@"theSocket6: %hu", [self localPortFromCFSocket6:theSocket6]);
- }
- theFlags |= kDidStartDelegate;
- return YES;
-
- Failed:
- if(errPtr) *errPtr = [self getSocketError];
- if(theSocket4 != NULL)
- {
- CFSocketInvalidate(theSocket4);
- CFRelease(theSocket4);
- theSocket4 = NULL;
- }
- if(theSocket6 != NULL)
- {
- CFSocketInvalidate(theSocket6);
- CFRelease(theSocket6);
- theSocket6 = NULL;
- }
- return NO;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Connecting
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr
- {
- return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr];
- }
- /**
- * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port.
- * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds.
- *
- * Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection,
- * specifically in the onSocketWillConnect: method.
- **/
- - (BOOL)connectToHost:(NSString *)hostname
- onPort:(UInt16)port
- withTimeout:(NSTimeInterval)timeout
- error:(NSError **)errPtr
- {
- if (theDelegate == NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect without a delegate. Set a delegate first."];
- }
- if (![self isDisconnected])
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect while connected or accepting connections. Disconnect first."];
- }
-
- // Clear queues (spurious read/write requests post disconnect)
- [self emptyQueues];
-
- if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed;
- if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed;
- if(![self configureStreamsAndReturnError:errPtr]) goto Failed;
- if(![self openStreamsAndReturnError:errPtr]) goto Failed;
-
- [self startConnectTimeout:timeout];
- theFlags |= kDidStartDelegate;
-
- return YES;
-
- Failed:
- [self close];
- return NO;
- }
- - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
- {
- return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:-1 error:errPtr];
- }
- /**
- * This method creates an initial CFSocket to the given address.
- * The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be
- * created from the low-level sockets after the connection succeeds.
- *
- * Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection,
- * specifically in the onSocketWillConnect: method.
- *
- * Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from
- * NSNetService addresses method.
- * If you have an existing struct sockaddr you can convert it to an NSData object like so:
- * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
- * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
- **/
- - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
- {
- return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:timeout error:errPtr];
- }
- /**
- * This method is similar to the one above, but allows you to specify which socket interface
- * the connection should run over. E.g. ethernet, wifi, bluetooth, etc.
- **/
- - (BOOL)connectToAddress:(NSData *)remoteAddr
- viaInterfaceAddress:(NSData *)interfaceAddr
- withTimeout:(NSTimeInterval)timeout
- error:(NSError **)errPtr
- {
- if (theDelegate == NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect without a delegate. Set a delegate first."];
- }
-
- if (![self isDisconnected])
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect while connected or accepting connections. Disconnect first."];
- }
-
- // Clear queues (spurious read/write requests post disconnect)
- [self emptyQueues];
-
- if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed;
- if(![self bindSocketToAddress:interfaceAddr error:errPtr]) goto Failed;
- if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed;
- if(![self configureSocketAndReturnError:errPtr]) goto Failed;
- if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed;
-
- [self startConnectTimeout:timeout];
- theFlags |= kDidStartDelegate;
-
- return YES;
-
- Failed:
- [self close];
- return NO;
- }
- - (void)startConnectTimeout:(NSTimeInterval)timeout
- {
- if(timeout >= 0.0)
- {
- theConnectTimer = [NSTimer timerWithTimeInterval:timeout
- target:self
- selector:@selector(doConnectTimeout:)
- userInfo:nil
- repeats:NO];
- [self runLoopAddTimer:theConnectTimer];
- }
- }
- - (void)endConnectTimeout
- {
- [theConnectTimer invalidate];
- theConnectTimer = nil;
- }
- - (void)doConnectTimeout:(NSTimer *)timer
- {
- #pragma unused(timer)
-
- [self endConnectTimeout];
- [self closeWithError:[self getConnectTimeoutError]];
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Socket Implementation
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * Creates the accept sockets.
- * Returns true if either IPv4 or IPv6 is created.
- * If either is missing, an error is returned (even though the method may return true).
- **/
- - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr
- {
- struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes];
- int addressFamily = pSockAddr->sa_family;
-
- CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault,
- addressFamily,
- SOCK_STREAM,
- 0,
- kCFSocketAcceptCallBack, // Callback flags
- (CFSocketCallBack)&MyCFSocketCallback, // Callback method
- &theContext);
- if(theSocket == NULL)
- {
- if(errPtr) *errPtr = [self getSocketError];
- }
-
- return theSocket;
- }
- - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr
- {
- struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes];
-
- if(pSockAddr->sa_family == AF_INET)
- {
- theSocket4 = CFSocketCreate(NULL, // Default allocator
- PF_INET, // Protocol Family
- SOCK_STREAM, // Socket Type
- IPPROTO_TCP, // Protocol
- kCFSocketConnectCallBack, // Callback flags
- (CFSocketCallBack)&MyCFSocketCallback, // Callback method
- &theContext); // Socket Context
-
- if(theSocket4 == NULL)
- {
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- }
- else if(pSockAddr->sa_family == AF_INET6)
- {
- theSocket6 = CFSocketCreate(NULL, // Default allocator
- PF_INET6, // Protocol Family
- SOCK_STREAM, // Socket Type
- IPPROTO_TCP, // Protocol
- kCFSocketConnectCallBack, // Callback flags
- (CFSocketCallBack)&MyCFSocketCallback, // Callback method
- &theContext); // Socket Context
-
- if(theSocket6 == NULL)
- {
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- }
- else
- {
- if (errPtr)
- {
- NSString *errMsg = @"Remote address is not IPv4 or IPv6";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg for