+++ /dev/null
-//
-// 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/
-//
-
-#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
-
-NSString *const AsyncSocketException = @"AsyncSocketException";
-NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain";
-
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
-// Mutex lock used by all instances of AsyncSocket, to protect getaddrinfo.
-// Prior to Mac OS X 10.5 this method was not thread-safe.
-static NSString *getaddrinfoLock = @"lock";
-#endif
-
-enum AsyncSocketFlags
-{
- kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled
- kDidPassConnectMethod = 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;
-
-// Socket Implementation
-- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr;
-- (BOOL)createSocketForAddress:(NSData *)remoteAddr 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)isSocketConnected;
-- (BOOL)areStreamsConnected;
-- (NSString *)connectedHost:(CFSocketRef)socket;
-- (UInt16)connectedPort:(CFSocketRef)socket;
-- (NSString *)localHost:(CFSocketRef)socket;
-- (UInt16)localPort:(CFSocketRef)socket;
-- (NSString *)addressHost:(CFDataRef)cfaddr;
-- (UInt16)addressPort:(CFDataRef)cfaddr;
-
-// 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;
- CFIndex bytesDone;
- NSTimeInterval timeout;
- CFIndex maxLength;
- long tag;
- NSData *term;
- BOOL readAllAvailableData;
-}
-- (id)initWithData:(NSMutableData *)d
- timeout:(NSTimeInterval)t
- tag:(long)i
- readAllAvailable:(BOOL)a
- terminator:(NSData *)e
- maxLength:(CFIndex)m;
-
-- (unsigned)readLengthForTerm;
-
-- (unsigned)prebufferReadLengthForTerm;
-- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes;
-@end
-
-@implementation AsyncReadPacket
-
-- (id)initWithData:(NSMutableData *)d
- timeout:(NSTimeInterval)t
- tag:(long)i
- readAllAvailable:(BOOL)a
- terminator:(NSData *)e
- maxLength:(CFIndex)m
-{
- if((self = [super init]))
- {
- buffer = [d retain];
- timeout = t;
- tag = i;
- readAllAvailableData = a;
- term = [e copy];
- bytesDone = 0;
- maxLength = m;
- }
- return self;
-}
-
-/**
- * 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.
- *
- * It is assumed the terminator has not already been read.
-**/
-- (unsigned)readLengthForTerm
-{
- NSAssert(term != nil, @"Searching for term in data when there is no term.");
-
- // 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.
-
- unsigned result = [term length];
-
- // Shortcut when term is a single byte
- if(result == 1) return result;
-
- // i = index within buffer at which to check data
- // j = length of term to check against
-
- // Note: Beware of implicit casting rules
- // This could give you -1: MAX(0, (0 - [term length] + 1));
-
- CFIndex i = MAX(0, (CFIndex)(bytesDone - [term length] + 1));
- CFIndex j = MIN([term length] - 1, bytesDone);
-
- while(i < bytesDone)
- {
- const void *subBuffer = [buffer bytes] + i;
-
- if(memcmp(subBuffer, [term bytes], j) == 0)
- {
- result = [term length] - j;
- break;
- }
-
- i++;
- j--;
- }
-
- if(maxLength > 0)
- return MIN(result, (maxLength - bytesDone));
- else
- return result;
-}
-
-/**
- * Assuming pre-buffering is enabled, returns the amount of data that can be read
- * without going over the maxLength.
-**/
-- (unsigned)prebufferReadLengthForTerm
-{
- if(maxLength > 0)
- return MIN(READALL_CHUNKSIZE, (maxLength - bytesDone));
- else
- return READALL_CHUNKSIZE;
-}
-
-/**
- * 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.
-**/
-- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes
-{
- NSAssert(term != nil, @"Searching for term in data when there is no term.");
-
- // 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.
-
- // Note: Beware of implicit casting rules
- // This could give you -1: MAX(0, 1 - 1 - [term length] + 1);
-
- CFIndex i = MAX(0, (CFIndex)(bytesDone - numBytes - [term length] + 1));
-
- while(i + [term length] <= bytesDone)
- {
- const void *subBuffer = [buffer bytes] + i;
-
- if(memcmp(subBuffer, [term bytes], [term length]) == 0)
- {
- return bytesDone - (i + [term length]);
- }
-
- i++;
- }
-
- return -1;
-}
-
-- (void)dealloc
-{
- [buffer release];
- [term release];
- [super dealloc];
-}
-
-@end
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark -
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * The AsyncWritePacket encompasses the instructions for any given write.
-**/
-@interface AsyncWritePacket : NSObject
-{
- @public
- NSData *buffer;
- CFIndex 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 retain];
- timeout = t;
- tag = i;
- bytesDone = 0;
- }
- return self;
-}
-
-- (void)dealloc
-{
- [buffer release];
- [super dealloc];
-}
-
-@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;
-}
-
-- (void)dealloc
-{
- [tlsSettings release];
- [super dealloc];
-}
-
-@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;
-
- 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 = self;
- theContext.retain = nil;
- theContext.release = nil;
- theContext.copyDescription = nil;
-
- // Default run loop modes
- theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain];
- }
- 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];
- [theReadQueue release];
- [theWriteQueue release];
- [theRunLoopModes release];
- [partialReadBuffer release];
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- [super dealloc];
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Accessors
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (long)userData
-{
- return theUserData;
-}
-
-- (void)setUserData:(long)userData
-{
- theUserData = userData;
-}
-
-- (id)delegate
-{
- return theDelegate;
-}
-
-- (void)setDelegate:(id)delegate
-{
- theDelegate = delegate;
-}
-
-- (BOOL)canSafelySetDelegate
-{
- return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil);
-}
-
-- (CFSocketRef)getCFSocket
-{
- if(theSocket4)
- return theSocket4;
- else
- return theSocket6;
-}
-
-- (CFReadStreamRef)getCFReadStream
-{
- return theReadStream;
-}
-
-- (CFWriteStreamRef)getCFWriteStream
-{
- return theWriteStream;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Progress
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
-{
- // 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]]) 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.
- BOOL hasTotal = (theCurrentRead->readAllAvailableData == NO && theCurrentRead->term == nil);
-
- CFIndex d = theCurrentRead->bytesDone;
- CFIndex t = hasTotal ? [theCurrentRead->buffer length] : 0;
- if (tag != NULL) *tag = theCurrentRead->tag;
- if (done != NULL) *done = d;
- if (total != NULL) *total = t;
- float ratio = (float)d/(float)t;
- return isnan(ratio) ? 1.0F : ratio; // 0 of 0 bytes is 100% done.
-}
-
-- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
-{
- // 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]]) return NAN;
-
- CFIndex d = theCurrentWrite->bytesDone;
- CFIndex 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
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopAddSource(theRunLoop, source, runLoopMode);
- }
-}
-
-- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopRemoveSource(theRunLoop, source, runLoopMode);
- }
-}
-
-- (void)runLoopAddTimer:(NSTimer *)timer
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
- }
-}
-
-- (void)runLoopRemoveTimer:(NSTimer *)timer
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
- }
-}
-
-- (void)runLoopUnscheduleReadStream
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode);
- }
- CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
-}
-
-- (void)runLoopUnscheduleWriteStream
-{
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode);
- }
- CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
-}
-
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Configuration
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * See the header file for a full explanation of pre-buffering.
-**/
-- (void)enablePreBuffering
-{
- 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];
-
- // We do not retain the timers - they get retained by the runloop when we add them as a source.
- // Since we're about to remove them as a source, we retain now, and release again below.
- [theReadTimer retain];
- [theWriteTimer retain];
-
- if(theReadTimer) [self runLoopRemoveTimer:theReadTimer];
- if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer];
-
- theRunLoop = [runLoop getCFRunLoop];
-
- if(theReadTimer) [self runLoopAddTimer:theReadTimer];
- if(theWriteTimer) [self runLoopAddTimer:theWriteTimer];
-
- // Release timers since we retained them above
- [theReadTimer release];
- [theWriteTimer release];
-
- 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];
-
- // We do not retain the timers - they get retained by the runloop when we add them as a source.
- // Since we're about to remove them as a source, we retain now, and release again below.
- [theReadTimer retain];
- [theWriteTimer retain];
-
- if(theReadTimer) [self runLoopRemoveTimer:theReadTimer];
- if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer];
-
- [theRunLoopModes release];
- theRunLoopModes = [runLoopModes copy];
-
- if(theReadTimer) [self runLoopAddTimer:theReadTimer];
- if(theWriteTimer) [self runLoopAddTimer:theWriteTimer];
-
- // Release timers since we retained them above
- [theReadTimer release];
- [theWriteTimer release];
-
- 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;
-}
-
-- (NSArray *)runLoopModes
-{
- return [[theRunLoopModes retain] autorelease];
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#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 (theSocket4 != NULL || theSocket6 != NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to accept while connected or accepting connections. Disconnect first."];
- }
-
- // 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];
-
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- @synchronized (getaddrinfoLock)
-#endif
- {
- struct addrinfo hints, *res, *res0;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_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];
- }
- }
-
- 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, (CFDataRef)address4);
- if (err != kCFSocketSuccess) goto Failed;
-
- //NSLog(@"theSocket4: %hu", [self localPort: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 localPort:theSocket4];
-
- struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes];
- pSockAddr6->sin6_port = htons(chosenPort);
- }
-
- if (theSocket6)
- {
- err = CFSocketSetAddress (theSocket6, (CFDataRef)address6);
- if (err != kCFSocketSuccess) goto Failed;
-
- //NSLog(@"theSocket6: %hu", [self localPort:theSocket6]);
- }
-
- theFlags |= kDidPassConnectMethod;
- 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(theSocket4 != NULL || theSocket6 != NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect while connected or accepting connections. Disconnect first."];
- }
-
- 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 |= kDidPassConnectMethod;
-
- return YES;
-
-Failed:
- [self close];
- return NO;
-}
-
-- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
-{
- return [self connectToAddress:remoteAddr 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
-{
- if (theDelegate == NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect without a delegate. Set a delegate first."];
- }
-
- if (theSocket4 != NULL || theSocket6 != NULL)
- {
- [NSException raise:AsyncSocketException
- format:@"Attempting to connect while connected or accepting connections. Disconnect first."];
- }
-
- if(![self createSocketForAddress:remoteAddr 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 |= kDidPassConnectMethod;
-
- 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
-{
- [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) *errPtr = [self getSocketError];
- return NO;
- }
-
- return YES;
-}
-
-/**
- * Adds the CFSocket's to the run-loop so that callbacks will work properly.
-**/
-- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr
-{
- // Get the CFRunLoop to which the socket should be attached.
- theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop];
-
- if(theSocket4)
- {
- theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0);
- [self runLoopAddSource:theSource4];
- }
-
- if(theSocket6)
- {
- theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0);
- [self runLoopAddSource:theSource6];
- }
-
- return YES;
-}
-
-/**
- * Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect.
- * Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened.
-**/
-- (BOOL)configureSocketAndReturnError:(NSError **)errPtr
-{
- // Call the delegate method for further configuration.
- if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)])
- {
- if([theDelegate onSocketWillConnect:self] == NO)
- {
- if (errPtr) *errPtr = [self getAbortError];
- return NO;
- }
- }
- return YES;
-}
-
-- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
-{
- // Start connecting to the given address in the background
- // The MyCFSocketCallback method will be called when the connection succeeds or fails
- if(theSocket4)
- {
- CFSocketError err = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, -1);
- if(err != kCFSocketSuccess)
- {
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- }
- else if(theSocket6)
- {
- CFSocketError err = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, -1);
- if(err != kCFSocketSuccess)
- {
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- }
-
- return YES;
-}
-
-/**
- * Attempt to make the new socket.
- * If an error occurs, ignore this event.
-**/
-- (void)doAcceptWithSocket:(CFSocketNativeHandle)newNative
-{
- // New socket inherits same delegate and run loop modes.
- // Note: We use [self class] to support subclassing AsyncSocket.
- AsyncSocket *newSocket = [[[[self class] alloc] initWithDelegate:theDelegate] autorelease];
- [newSocket setRunLoopModes:theRunLoopModes];
-
- if(newSocket)
- {
- if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)])
- [theDelegate onSocket:self didAcceptNewSocket:newSocket];
-
- NSRunLoop *runLoop = nil;
- if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)])
- runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket];
-
- BOOL pass = YES;
-
- if(pass && ![newSocket createStreamsFromNative:newNative error:nil]) pass = NO;
- if(pass && ![newSocket attachStreamsToRunLoop:runLoop error:nil]) pass = NO;
- if(pass && ![newSocket configureStreamsAndReturnError:nil]) pass = NO;
- if(pass && ![newSocket openStreamsAndReturnError:nil]) pass = NO;
-
- if(pass)
- newSocket->theFlags |= kDidPassConnectMethod;
- else {
- // No NSError, but errors will still get logged from the above functions.
- [newSocket close];
- }
-
- }
-}
-
-/**
- * Description forthcoming...
-**/
-- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError
-{
- NSParameterAssert ((sock == theSocket4) || (sock == theSocket6));
-
- if(socketError == kCFSocketTimeout || socketError == kCFSocketError)
- {
- [self closeWithError:[self getSocketError]];
- return;
- }
-
- // Get the underlying native (BSD) socket
- CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock);
-
- // Setup the socket so that invalidating the socket will not close the native socket
- CFSocketSetSocketFlags(sock, 0);
-
- // Invalidate and release the CFSocket - All we need from here on out is the nativeSocket
- // Note: If we don't invalidate the socket (leaving the native socket open)
- // then theReadStream and theWriteStream won't function properly.
- // Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted.
- // I'm not entirely sure why this is, but I'm guessing that events on the socket fire to the CFSocket we created,
- // as opposed to the CFReadStream/CFWriteStream.
-
- CFSocketInvalidate(sock);
- CFRelease(sock);
- theSocket4 = NULL;
- theSocket6 = NULL;
-
- NSError *err;
- BOOL pass = YES;
-
- if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO;
- if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO;
- if(pass && ![self openStreamsAndReturnError:&err]) pass = NO;
-
- if(!pass)
- {
- [self closeWithError:err];
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Stream Implementation
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Creates the CFReadStream and CFWriteStream from the given native socket.
- * The CFSocket may be extracted from either stream after the streams have been opened.
- *
- * Note: The given native socket must already be connected!
-**/
-- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr
-{
- // Create the socket & streams.
- CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream);
- if (theReadStream == NULL || theWriteStream == NULL)
- {
- NSError *err = [self getStreamError];
-
- NSLog (@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err);
-
- if (errPtr) *errPtr = err;
- return NO;
- }
-
- // Ensure the CF & BSD socket is closed when the streams are closed.
- CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
- CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
-
- return YES;
-}
-
-/**
- * Creates the CFReadStream and CFWriteStream from the given hostname and port number.
- * The CFSocket may be extracted from either stream after the streams have been opened.
-**/
-- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr
-{
- // Create the socket & streams.
- CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostname, port, &theReadStream, &theWriteStream);
- if (theReadStream == NULL || theWriteStream == NULL)
- {
- if (errPtr) *errPtr = [self getStreamError];
- return NO;
- }
-
- // Ensure the CF & BSD socket is closed when the streams are closed.
- CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
- CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
-
- return YES;
-}
-
-- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr
-{
- // Get the CFRunLoop to which the socket should be attached.
- theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop];
-
- // Setup read stream callbacks
-
- CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable |
- kCFStreamEventErrorOccurred |
- kCFStreamEventEndEncountered |
- kCFStreamEventOpenCompleted;
-
- if (!CFReadStreamSetClient(theReadStream,
- readStreamEvents,
- (CFReadStreamClientCallBack)&MyCFReadStreamCallback,
- (CFStreamClientContext *)(&theContext)))
- {
- NSError *err = [self getStreamError];
-
- NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self);
- NSLog (@"Error: %@", err);
-
- if (errPtr) *errPtr = err;
- return NO;
- }
-
- // Setup write stream callbacks
-
- CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes |
- kCFStreamEventErrorOccurred |
- kCFStreamEventEndEncountered |
- kCFStreamEventOpenCompleted;
-
- if (!CFWriteStreamSetClient (theWriteStream,
- writeStreamEvents,
- (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback,
- (CFStreamClientContext *)(&theContext)))
- {
- NSError *err = [self getStreamError];
-
- NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self);
- NSLog (@"Error: %@", err);
-
- if (errPtr) *errPtr = err;
- return NO;
- }
-
- // Add read and write streams to run loop
-
- unsigned i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, runLoopMode);
- CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, runLoopMode);
- }
-
- return YES;
-}
-
-/**
- * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect.
- * Note that the CFSocket and CFNativeSocket will not be available until after the connection is opened.
-**/
-- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr
-{
- // Call the delegate method for further configuration.
- if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)])
- {
- if([theDelegate onSocketWillConnect:self] == NO)
- {
- if (errPtr) *errPtr = [self getAbortError];
- return NO;
- }
- }
- return YES;
-}
-
-- (BOOL)openStreamsAndReturnError:(NSError **)errPtr
-{
- BOOL pass = YES;
-
- if(pass && !CFReadStreamOpen (theReadStream))
- {
- NSLog (@"AsyncSocket %p couldn't open read stream,", self);
- pass = NO;
- }
-
- if(pass && !CFWriteStreamOpen (theWriteStream))
- {
- NSLog (@"AsyncSocket %p couldn't open write stream,", self);
- pass = NO;
- }
-
- if(!pass)
- {
- if (errPtr) *errPtr = [self getStreamError];
- }
-
- return pass;
-}
-
-/**
- * Called when read or write streams open.
- * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready.
-**/
-- (void)doStreamOpen
-{
- NSError *err = nil;
- if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite))
- {
- // Get the socket.
- if (![self setSocketFromStreamsAndReturnError: &err])
- {
- NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err);
- [self closeWithError:err];
- return;
- }
-
- // Stop the connection attempt timeout timer
- [self endConnectTimeout];
-
- if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)])
- {
- [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]];
- }
-
- // Immediately deal with any already-queued requests.
- [self maybeDequeueRead];
- [self maybeDequeueWrite];
- }
-}
-
-- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr
-{
- // Get the CFSocketNativeHandle from theReadStream
- CFSocketNativeHandle native;
- CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle);
- if(nativeProp == NULL)
- {
- if (errPtr) *errPtr = [self getStreamError];
- return NO;
- }
-
- CFDataGetBytes(nativeProp, CFRangeMake(0, CFDataGetLength(nativeProp)), (UInt8 *)&native);
- CFRelease(nativeProp);
-
- CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL);
- if(theSocket == NULL)
- {
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
-
- // Determine whether the connection was IPv4 or IPv6
- CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket);
- if(peeraddr == NULL)
- {
- NSLog(@"AsyncSocket couldn't determine IP version of socket");
-
- CFRelease(theSocket);
-
- if (errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr);
-
- if(sa->sa_family == AF_INET)
- {
- theSocket4 = theSocket;
- }
- else
- {
- theSocket6 = theSocket;
- }
-
- CFRelease(peeraddr);
-
- return YES;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Disconnect Implementation
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Sends error message and disconnects
-- (void)closeWithError:(NSError *)err
-{
- theFlags |= kClosingWithError;
-
- if (theFlags & kDidPassConnectMethod)
- {
- // Try to salvage what data we can.
- [self recoverUnreadData];
-
- // Let the delegate know, so it can try to recover if it likes.
- if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)])
- {
- [theDelegate onSocket:self willDisconnectWithError:err];
- }
- }
- [self close];
-}
-
-// Prepare partially read data for recovery.
-- (void)recoverUnreadData
-{
- if(theCurrentRead != nil)
- {
- // We never finished the current read.
- // Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet.
-
- if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0))
- {
- // We need to move its data into the front of the partial read buffer.
-
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0)
- withBytes:[theCurrentRead->buffer bytes]
- length:theCurrentRead->bytesDone];
- }
- }
-
- [self emptyQueues];
-}
-
-- (void)emptyQueues
-{
- if (theCurrentRead != nil) [self endCurrentRead];
- if (theCurrentWrite != nil) [self endCurrentWrite];
-
- [theReadQueue removeAllObjects];
- [theWriteQueue removeAllObjects];
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil];
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil];
-
- theFlags &= ~kDequeueReadScheduled;
- theFlags &= ~kDequeueWriteScheduled;
-}
-
-/**
- * Disconnects. This is called for both error and clean disconnections.
-**/
-- (void)close
-{
- // Empty queues
- [self emptyQueues];
-
- // Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error)
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0];
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil];
-
- // Stop the connection attempt timeout timer
- if (theConnectTimer != nil)
- {
- [self endConnectTimeout];
- }
-
- // Close streams.
- if (theReadStream != NULL)
- {
- [self runLoopUnscheduleReadStream];
- CFReadStreamClose(theReadStream);
- CFRelease(theReadStream);
- theReadStream = NULL;
- }
- if (theWriteStream != NULL)
- {
- [self runLoopUnscheduleWriteStream];
- CFWriteStreamClose(theWriteStream);
- CFRelease(theWriteStream);
- theWriteStream = NULL;
- }
-
- // Close sockets.
- if (theSocket4 != NULL)
- {
- CFSocketInvalidate (theSocket4);
- CFRelease (theSocket4);
- theSocket4 = NULL;
- }
- if (theSocket6 != NULL)
- {
- CFSocketInvalidate (theSocket6);
- CFRelease (theSocket6);
- theSocket6 = NULL;
- }
- if (theSource4 != NULL)
- {
- [self runLoopRemoveSource:theSource4];
- CFRelease (theSource4);
- theSource4 = NULL;
- }
- if (theSource6 != NULL)
- {
- [self runLoopRemoveSource:theSource6];
- CFRelease (theSource6);
- theSource6 = NULL;
- }
- theRunLoop = NULL;
-
- // If the client has passed the connect/accept method, then the connection has at least begun.
- // Notify delegate that it is now ending.
- BOOL shouldCallDelegate = (theFlags & kDidPassConnectMethod);
-
- // Clear all flags (except the pre-buffering flag, which should remain as is)
- theFlags &= kEnablePreBuffering;
-
- if (shouldCallDelegate)
- {
- if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)])
- {
- [theDelegate onSocketDidDisconnect:self];
- }
- }
-
- // Do not access any instance variables after calling onSocketDidDisconnect.
- // This gives the delegate freedom to release us without returning here and crashing.
-}
-
-/**
- * Disconnects immediately. Any pending reads or writes are dropped.
-**/
-- (void)disconnect
-{
- [self close];
-}
-
-/**
- * Diconnects after all pending reads have completed.
-**/
-- (void)disconnectAfterReading
-{
- theFlags |= (kForbidReadsWrites | kDisconnectAfterReads);
-
- [self maybeScheduleDisconnect];
-}
-
-/**
- * Disconnects after all pending writes have completed.
-**/
-- (void)disconnectAfterWriting
-{
- theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites);
-
- [self maybeScheduleDisconnect];
-}
-
-/**
- * Disconnects after all pending reads and writes have completed.
-**/
-- (void)disconnectAfterReadingAndWriting
-{
- theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
-
- [self maybeScheduleDisconnect];
-}
-
-/**
- * Schedules a call to disconnect if possible.
- * That is, if all writes have completed, and we're set to disconnect after writing,
- * or if all reads have completed, and we're set to disconnect after reading.
-**/
-- (void)maybeScheduleDisconnect
-{
- BOOL shouldDisconnect = NO;
-
- if(theFlags & kDisconnectAfterReads)
- {
- if(([theReadQueue count] == 0) && (theCurrentRead == nil))
- {
- if(theFlags & kDisconnectAfterWrites)
- {
- if(([theWriteQueue count] == 0) && (theCurrentWrite == nil))
- {
- shouldDisconnect = YES;
- }
- }
- else
- {
- shouldDisconnect = YES;
- }
- }
- }
- else if(theFlags & kDisconnectAfterWrites)
- {
- if(([theWriteQueue count] == 0) && (theCurrentWrite == nil))
- {
- shouldDisconnect = YES;
- }
- }
-
- if(shouldDisconnect)
- {
- [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- }
-}
-
-/**
- * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read
- * any data that's left on the socket.
-**/
-- (NSData *)unreadData
-{
- // Ensure this method will only return data in the event of an error
- if(!(theFlags & kClosingWithError)) return nil;
-
- if(theReadStream == NULL) return nil;
-
- CFIndex totalBytesRead = [partialReadBuffer length];
- BOOL error = NO;
- while(!error && CFReadStreamHasBytesAvailable(theReadStream))
- {
- [partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE];
-
- // Number of bytes to read is space left in packet buffer.
- CFIndex bytesToRead = [partialReadBuffer length] - totalBytesRead;
-
- // Read data into packet buffer
- UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead );
- CFIndex bytesRead = CFReadStreamRead(theReadStream, packetbuf, bytesToRead);
-
- // Check results
- if(bytesRead < 0)
- {
- error = YES;
- }
- else
- {
- totalBytesRead += bytesRead;
- }
- }
-
- [partialReadBuffer setLength:totalBytesRead];
-
- return partialReadBuffer;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Errors
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Returns a standard error object for the current errno value.
- * Errno is used for low-level BSD socket errors.
-**/
-- (NSError *)getErrnoError
-{
- NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)];
- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
-}
-
-/**
- * Returns a standard error message for a CFSocket error.
- * Unfortunately, CFSocket offers no feedback on its errors.
-**/
-- (NSError *)getSocketError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"General CFSocket error", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info];
-}
-
-- (NSError *)getStreamError
-{
- CFStreamError err;
- if (theReadStream != NULL)
- {
- err = CFReadStreamGetError (theReadStream);
- if (err.error != 0) return [self errorFromCFStreamError: err];
- }
-
- if (theWriteStream != NULL)
- {
- err = CFWriteStreamGetError (theWriteStream);
- if (err.error != 0) return [self errorFromCFStreamError: err];
- }
-
- return nil;
-}
-
-/**
- * Returns a standard AsyncSocket abort error.
-**/
-- (NSError *)getAbortError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"Connection canceled", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info];
-}
-
-/**
- * Returns a standard AsyncSocket connect timeout error.
-**/
-- (NSError *)getConnectTimeoutError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"Attempt to connect to host timed out", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info];
-}
-
-/**
- * Returns a standard AsyncSocket maxed out error.
-**/
-- (NSError *)getReadMaxedOutError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"Read operation reached set maximum length", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info];
-}
-
-/**
- * Returns a standard AsyncSocket read timeout error.
-**/
-- (NSError *)getReadTimeoutError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"Read operation timed out", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info];
-}
-
-/**
- * Returns a standard AsyncSocket write timeout error.
-**/
-- (NSError *)getWriteTimeoutError
-{
- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError",
- @"AsyncSocket", [NSBundle mainBundle],
- @"Write operation timed out", nil);
-
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info];
-}
-
-- (NSError *)errorFromCFStreamError:(CFStreamError)err
-{
- if (err.domain == 0 && err.error == 0) return nil;
-
- // Can't use switch; these constants aren't int literals.
- NSString *domain = @"CFStreamError (unlisted domain)";
- NSString *message = nil;
-
- if(err.domain == kCFStreamErrorDomainPOSIX) {
- domain = NSPOSIXErrorDomain;
- }
- else if(err.domain == kCFStreamErrorDomainMacOSStatus) {
- domain = NSOSStatusErrorDomain;
- }
- else if(err.domain == kCFStreamErrorDomainMach) {
- domain = NSMachErrorDomain;
- }
- else if(err.domain == kCFStreamErrorDomainNetDB)
- {
- domain = @"kCFStreamErrorDomainNetDB";
- message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding];
- }
- else if(err.domain == kCFStreamErrorDomainNetServices) {
- domain = @"kCFStreamErrorDomainNetServices";
- }
- else if(err.domain == kCFStreamErrorDomainSOCKS) {
- domain = @"kCFStreamErrorDomainSOCKS";
- }
- else if(err.domain == kCFStreamErrorDomainSystemConfiguration) {
- domain = @"kCFStreamErrorDomainSystemConfiguration";
- }
- else if(err.domain == kCFStreamErrorDomainSSL) {
- domain = @"kCFStreamErrorDomainSSL";
- }
-
- NSDictionary *info = nil;
- if(message != nil)
- {
- info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey];
- }
- return [NSError errorWithDomain:domain code:err.error userInfo:info];
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Diagnostics
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (BOOL)isConnected
-{
- return [self isSocketConnected] && [self areStreamsConnected];
-}
-
-- (NSString *)connectedHost
-{
- if(theSocket4)
- return [self connectedHost:theSocket4];
- else
- return [self connectedHost:theSocket6];
-}
-
-- (UInt16)connectedPort
-{
- if(theSocket4)
- return [self connectedPort:theSocket4];
- else
- return [self connectedPort:theSocket6];
-}
-
-- (NSString *)localHost
-{
- if(theSocket4)
- return [self localHost:theSocket4];
- else
- return [self localHost:theSocket6];
-}
-
-- (UInt16)localPort
-{
- if(theSocket4)
- return [self localPort:theSocket4];
- else
- return [self localPort:theSocket6];
-}
-
-- (NSString *)connectedHost:(CFSocketRef)theSocket
-{
- if (theSocket == NULL) return nil;
-
- CFDataRef peeraddr;
- NSString *peerstr = nil;
-
- if((peeraddr = CFSocketCopyPeerAddress(theSocket)))
- {
- peerstr = [self addressHost:peeraddr];
- CFRelease (peeraddr);
- }
-
- return peerstr;
-}
-
-- (UInt16)connectedPort:(CFSocketRef)theSocket
-{
- if (theSocket == NULL) return 0;
-
- CFDataRef peeraddr;
- UInt16 peerport = 0;
-
- if((peeraddr = CFSocketCopyPeerAddress(theSocket)))
- {
- peerport = [self addressPort:peeraddr];
- CFRelease (peeraddr);
- }
-
- return peerport;
-}
-
-- (NSString *)localHost:(CFSocketRef)theSocket
-{
- if (theSocket == NULL) return nil;
-
- CFDataRef selfaddr;
- NSString *selfstr = nil;
-
- if((selfaddr = CFSocketCopyAddress(theSocket)))
- {
- selfstr = [self addressHost:selfaddr];
- CFRelease (selfaddr);
- }
-
- return selfstr;
-}
-
-- (UInt16)localPort:(CFSocketRef)theSocket
-{
- if (theSocket == NULL) return 0;
-
- CFDataRef selfaddr;
- UInt16 selfport = 0;
-
- if ((selfaddr = CFSocketCopyAddress(theSocket)))
- {
- selfport = [self addressPort:selfaddr];
- CFRelease (selfaddr);
- }
-
- return selfport;
-}
-
-- (NSString *)addressHost:(CFDataRef)cfaddr
-{
- if (cfaddr == NULL) return nil;
-
- char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
- struct sockaddr *pSockAddr = (struct sockaddr *) CFDataGetBytePtr (cfaddr);
- struct sockaddr_in *pSockAddrV4 = (struct sockaddr_in *)pSockAddr;
- struct sockaddr_in6 *pSockAddrV6 = (struct sockaddr_in6 *)pSockAddr;
-
- const void *pAddr = (pSockAddr->sa_family == AF_INET) ?
- (void *)(&(pSockAddrV4->sin_addr)) :
- (void *)(&(pSockAddrV6->sin6_addr));
-
- const char *pStr = inet_ntop (pSockAddr->sa_family, pAddr, addrBuf, sizeof(addrBuf));
- if (pStr == NULL) [NSException raise: NSInternalInconsistencyException
- format: @"Cannot convert address to string."];
-
- return [NSString stringWithCString:pStr encoding:NSASCIIStringEncoding];
-}
-
-- (UInt16)addressPort:(CFDataRef)cfaddr
-{
- if (cfaddr == NULL) return 0;
-
- struct sockaddr_in *pAddr = (struct sockaddr_in *) CFDataGetBytePtr (cfaddr);
- return ntohs (pAddr->sin_port);
-}
-
-- (NSData *)connectedAddress
-{
- CFSocketRef theSocket;
-
- if (theSocket4)
- theSocket = theSocket4;
- else
- theSocket = theSocket6;
-
- if (theSocket == NULL) return nil;
-
- CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket);
-
- if (peeraddr == NULL) return nil;
-
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(peeraddr) length:CFDataGetLength(peeraddr)];
- CFRelease(peeraddr);
- return result;
-#else
- return [(NSData *)NSMakeCollectable(peeraddr) autorelease];
-#endif
-}
-
-- (NSData *)localAddress
-{
- CFSocketRef theSocket;
-
- if (theSocket4)
- theSocket = theSocket4;
- else
- theSocket = theSocket6;
-
- if (theSocket == NULL) return nil;
-
- CFDataRef selfaddr = CFSocketCopyAddress(theSocket);
-
- if (selfaddr == NULL) return nil;
-
-#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(selfaddr) length:CFDataGetLength(selfaddr)];
- CFRelease(selfaddr);
- return result;
-#else
- return [(NSData *)NSMakeCollectable(selfaddr) autorelease];
-#endif
-}
-
-- (BOOL)isIPv4
-{
- return (theSocket4 != NULL);
-}
-
-- (BOOL)isIPv6
-{
- return (theSocket6 != NULL);
-}
-
-- (BOOL)isSocketConnected
-{
- if(theSocket4 != NULL)
- return CFSocketIsValid(theSocket4);
- else if(theSocket6 != NULL)
- return CFSocketIsValid(theSocket6);
- else
- return NO;
-}
-
-- (BOOL)areStreamsConnected
-{
- CFStreamStatus s;
-
- if (theReadStream != NULL)
- {
- s = CFReadStreamGetStatus (theReadStream);
- if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) )
- return NO;
- }
- else return NO;
-
- if (theWriteStream != NULL)
- {
- s = CFWriteStreamGetStatus (theWriteStream);
- if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) )
- return NO;
- }
- else return NO;
-
- return YES;
-}
-
-- (NSString *)description
-{
- static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"};
- CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0;
- CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0;
-
- NSString *peerstr, *selfstr;
- CFDataRef peeraddr4 = NULL, peeraddr6 = NULL, selfaddr4 = NULL, selfaddr6 = NULL;
-
- if (theSocket4 || theSocket6)
- {
- if (theSocket4) peeraddr4 = CFSocketCopyPeerAddress(theSocket4);
- if (theSocket6) peeraddr6 = CFSocketCopyPeerAddress(theSocket6);
-
- if(theSocket4 && theSocket6)
- {
- peerstr = [NSString stringWithFormat: @"%@/%@ %u",
- [self addressHost:peeraddr4], [self addressHost:peeraddr6], [self addressPort:peeraddr4]];
- }
- else if(theSocket4)
- {
- peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr4], [self addressPort:peeraddr4]];
- }
- else
- {
- peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr6], [self addressPort:peeraddr6]];
- }
-
- if(peeraddr4) CFRelease(peeraddr4);
- if(peeraddr6) CFRelease(peeraddr6);
- peeraddr4 = NULL;
- peeraddr6 = NULL;
- }
- else peerstr = @"nowhere";
-
- if (theSocket4 || theSocket6)
- {
- if (theSocket4) selfaddr4 = CFSocketCopyAddress (theSocket4);
- if (theSocket6) selfaddr6 = CFSocketCopyAddress (theSocket6);
-
- if (theSocket4 && theSocket6)
- {
- selfstr = [NSString stringWithFormat: @"%@/%@ %u",
- [self addressHost:selfaddr4], [self addressHost:selfaddr6], [self addressPort:selfaddr4]];
- }
- else if (theSocket4)
- {
- selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr4], [self addressPort:selfaddr4]];
- }
- else
- {
- selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr6], [self addressPort:selfaddr6]];
- }
-
- if(selfaddr4) CFRelease(selfaddr4);
- if(selfaddr6) CFRelease(selfaddr6);
- selfaddr4 = NULL;
- selfaddr6 = NULL;
- }
- else selfstr = @"nowhere";
-
- NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150];
-
- [ms appendString:[NSString stringWithFormat:@"<AsyncSocket %p", self]];
- [ms appendString:[NSString stringWithFormat:@" local %@ remote %@ ", selfstr, peerstr]];
-
- unsigned readQueueCount = (unsigned)[theReadQueue count];
- unsigned writeQueueCount = (unsigned)[theWriteQueue count];
-
- [ms appendString:[NSString stringWithFormat:@"has queued %u reads %u writes, ", readQueueCount, writeQueueCount]];
-
- if (theCurrentRead == nil)
- [ms appendString: @"no current read, "];
- else
- {
- int percentDone;
- if ([theCurrentRead->buffer length] != 0)
- percentDone = (float)theCurrentRead->bytesDone /
- (float)[theCurrentRead->buffer length] * 100.0F;
- else
- percentDone = 100.0F;
-
- [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ",
- (unsigned int)[theCurrentRead->buffer length],
- theCurrentRead->bytesDone ? percentDone : 0]];
- }
-
- if (theCurrentWrite == nil)
- [ms appendString: @"no current write, "];
- else
- {
- int percentDone;
- if ([theCurrentWrite->buffer length] != 0)
- percentDone = (float)theCurrentWrite->bytesDone /
- (float)[theCurrentWrite->buffer length] * 100.0F;
- else
- percentDone = 100.0F;
-
- [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ",
- (unsigned int)[theCurrentWrite->buffer length],
- theCurrentWrite->bytesDone ? percentDone : 0]];
- }
-
- [ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]];
- [ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]];
-
- if(theFlags & kDisconnectAfterReads)
- {
- if(theFlags & kDisconnectAfterWrites)
- [ms appendString: @", will disconnect after reads & writes"];
- else
- [ms appendString: @", will disconnect after reads"];
- }
- else if(theFlags & kDisconnectAfterWrites)
- {
- [ms appendString: @", will disconnect after writes"];
- }
-
- if (![self isConnected]) [ms appendString: @", not connected"];
-
- [ms appendString:@">"];
-
- return [ms autorelease];
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Reading
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
-{
- if(length == 0) return;
- if(theFlags & kForbidReadsWrites) return;
-
- NSMutableData *buffer = [[NSMutableData alloc] initWithLength:length];
- AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer
- timeout:timeout
- tag:tag
- readAllAvailable:NO
- terminator:nil
- maxLength:length];
-
- [theReadQueue addObject:packet];
- [self scheduleDequeueRead];
-
- [packet release];
- [buffer release];
-}
-
-- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
-{
- [self readDataToData:data withTimeout:timeout maxLength:-1 tag:tag];
-}
-
-- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag
-{
- if(data == nil || [data length] == 0) return;
- if(length >= 0 && length < [data length]) return;
- if(theFlags & kForbidReadsWrites) return;
-
- NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0];
- AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer
- timeout:timeout
- tag:tag
- readAllAvailable:NO
- terminator:data
- maxLength:length];
-
- [theReadQueue addObject:packet];
- [self scheduleDequeueRead];
-
- [packet release];
- [buffer release];
-}
-
-- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
-{
- if (theFlags & kForbidReadsWrites) return;
-
- NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0];
- AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer
- timeout:timeout
- tag:tag
- readAllAvailable:YES
- terminator:nil
- maxLength:-1];
-
- [theReadQueue addObject:packet];
- [self scheduleDequeueRead];
-
- [packet release];
- [buffer release];
-}
-
-/**
- * Puts a maybeDequeueRead on the run loop.
- * An assumption here is that selectors will be performed consecutively within their priority.
-**/
-- (void)scheduleDequeueRead
-{
- if((theFlags & kDequeueReadScheduled) == 0)
- {
- theFlags |= kDequeueReadScheduled;
- [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- }
-}
-
-/**
- * This method starts a new read, if needed.
- * It is called when a user requests a read,
- * or when a stream opens that may have requested reads sitting in the queue, etc.
-**/
-- (void)maybeDequeueRead
-{
- // Unset the flag indicating a call to this method is scheduled
- theFlags &= ~kDequeueReadScheduled;
-
- // If we're not currently processing a read AND we have an available read stream
- if((theCurrentRead == nil) && (theReadStream != NULL))
- {
- if([theReadQueue count] > 0)
- {
- // Dequeue the next object in the write queue
- theCurrentRead = [[theReadQueue objectAtIndex:0] retain];
- [theReadQueue removeObjectAtIndex:0];
-
- if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]])
- {
- // Attempt to start TLS
- theFlags |= kStartingReadTLS;
-
- // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set
- [self maybeStartTLS];
- }
- else
- {
- // Start time-out timer
- if(theCurrentRead->timeout >= 0.0)
- {
- theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout
- target:self
- selector:@selector(doReadTimeout:)
- userInfo:nil
- repeats:NO];
- [self runLoopAddTimer:theReadTimer];
- }
-
- // Immediately read, if possible
- [self doBytesAvailable];
- }
- }
- else if(theFlags & kDisconnectAfterReads)
- {
- if(theFlags & kDisconnectAfterWrites)
- {
- if(([theWriteQueue count] == 0) && (theCurrentWrite == nil))
- {
- [self disconnect];
- }
- }
- else
- {
- [self disconnect];
- }
- }
- }
-}
-
-/**
- * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable().
- * This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag.
-**/
-- (BOOL)hasBytesAvailable
-{
- if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0))
- {
- return YES;
- }
- else
- {
- return CFReadStreamHasBytesAvailable(theReadStream);
- }
-}
-
-/**
- * Call this method in doBytesAvailable instead of CFReadStreamRead().
- * This method support pre-buffering properly.
-**/
-- (CFIndex)readIntoBuffer:(UInt8 *)buffer maxLength:(CFIndex)length
-{
- if([partialReadBuffer length] > 0)
- {
- // Determine the maximum amount of data to read
- CFIndex bytesToRead = MIN(length, [partialReadBuffer length]);
-
- // Copy the bytes from the buffer
- memcpy(buffer, [partialReadBuffer bytes], bytesToRead);
-
- // Remove the copied bytes from the buffer
- [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0];
-
- return bytesToRead;
- }
- else
- {
- // Unset the "has-bytes-available" flag
- theFlags &= ~kSocketHasBytesAvailable;
-
- return CFReadStreamRead(theReadStream, buffer, length);
- }
-}
-
-/**
- * This method is called when a new read is taken from the read queue or when new data becomes available on the stream.
-**/
-- (void)doBytesAvailable
-{
- // If data is available on the stream, but there is no read request, then we don't need to process the data yet.
- // Also, if there is a read request, but no read stream setup yet, we can't process any data yet.
- if((theCurrentRead != nil) && (theReadStream != NULL))
- {
- // Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet)
-
- CFIndex totalBytesRead = 0;
-
- BOOL done = NO;
- BOOL socketError = NO;
- BOOL maxoutError = NO;
-
- while(!done && !socketError && !maxoutError && [self hasBytesAvailable])
- {
- BOOL didPreBuffer = NO;
-
- // There are 3 types of read packets:
- //
- // 1) Read a specific length of data.
- // 2) Read all available data.
- // 3) Read up to a particular terminator.
-
- if(theCurrentRead->readAllAvailableData == YES)
- {
- // We're reading all available data.
- //
- // Make sure there is at least READALL_CHUNKSIZE bytes available.
- // We don't want to increase the buffer any more than this or we'll waste space.
- // With prebuffering it's possible to read in a small chunk on the first read.
-
- unsigned buffInc = READALL_CHUNKSIZE - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone);
- [theCurrentRead->buffer increaseLengthBy:buffInc];
- }
- else if(theCurrentRead->term != nil)
- {
- // We're reading up to a terminator.
- //
- // We may only want to read a few bytes.
- // Just enough to ensure we don't go past our term or over our max limit.
- // Unless pre-buffering is enabled, in which case we may want to read in a larger chunk.
-
- // If we already have data pre-buffered, we obviously don't want to pre-buffer it again.
- // So in this case we'll just read as usual.
-
- if(([partialReadBuffer length] > 0) || !(theFlags & kEnablePreBuffering))
- {
- unsigned maxToRead = [theCurrentRead readLengthForTerm];
-
- unsigned bufInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone);
- [theCurrentRead->buffer increaseLengthBy:bufInc];
- }
- else
- {
- didPreBuffer = YES;
- unsigned maxToRead = [theCurrentRead prebufferReadLengthForTerm];
-
- unsigned buffInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone);
- [theCurrentRead->buffer increaseLengthBy:buffInc];
-
- }
- }
-
- // Number of bytes to read is space left in packet buffer.
- CFIndex bytesToRead = [theCurrentRead->buffer length] - theCurrentRead->bytesDone;
-
- // Read data into packet buffer
- UInt8 *subBuffer = (UInt8 *)([theCurrentRead->buffer mutableBytes] + theCurrentRead->bytesDone);
- CFIndex bytesRead = [self readIntoBuffer:subBuffer maxLength:bytesToRead];
-
- // Check results
- if(bytesRead < 0)
- {
- socketError = YES;
- }
- else
- {
- // Update total amount read for the current read
- theCurrentRead->bytesDone += bytesRead;
-
- // Update total amount read in this method invocation
- totalBytesRead += bytesRead;
- }
-
- // Is packet done?
- if(theCurrentRead->readAllAvailableData != YES)
- {
- if(theCurrentRead->term != nil)
- {
- if(didPreBuffer)
- {
- // Search for the terminating sequence within the big chunk we just read.
- CFIndex overflow = [theCurrentRead searchForTermAfterPreBuffering:bytesRead];
-
- if(overflow > 0)
- {
- // Copy excess data into partialReadBuffer
- NSMutableData *buffer = theCurrentRead->buffer;
- const void *overflowBuffer = [buffer bytes] + theCurrentRead->bytesDone - overflow;
-
- [partialReadBuffer appendBytes:overflowBuffer length:overflow];
-
- // Update the bytesDone variable.
- // Note: The completeCurrentRead method will trim the buffer for us.
- theCurrentRead->bytesDone -= overflow;
- }
-
- done = (overflow >= 0);
- }
- else
- {
- // Search for the terminating sequence at the end of the buffer
- int termlen = [theCurrentRead->term length];
- if(theCurrentRead->bytesDone >= termlen)
- {
- const void *buf = [theCurrentRead->buffer bytes] + (theCurrentRead->bytesDone - termlen);
- const void *seq = [theCurrentRead->term bytes];
- done = (memcmp (buf, seq, termlen) == 0);
- }
- }
-
- if(!done && theCurrentRead->maxLength >= 0 && theCurrentRead->bytesDone >= theCurrentRead->maxLength)
- {
- // There's a set maxLength, and we've reached that maxLength without completing the read
- maxoutError = YES;
- }
- }
- else
- {
- // Done when (sized) buffer is full.
- done = ([theCurrentRead->buffer length] == theCurrentRead->bytesDone);
- }
- }
- // else readAllAvailable doesn't end until all readable is read.
- }
-
- if(theCurrentRead->readAllAvailableData && theCurrentRead->bytesDone > 0)
- {
- // Ran out of bytes, so the "read-all-available-data" type packet is done
- done = YES;
- }
-
- if(done)
- {
- [self completeCurrentRead];
- if (!socketError) [self scheduleDequeueRead];
- }
- else if(totalBytesRead > 0)
- {
- // We're not done with the readToLength or readToData yet, but we have read in some bytes
- if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)])
- {
- [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag];
- }
- }
-
- if(socketError)
- {
- CFStreamError err = CFReadStreamGetError(theReadStream);
- [self closeWithError:[self errorFromCFStreamError:err]];
- return;
- }
- if(maxoutError)
- {
- [self closeWithError:[self getReadMaxedOutError]];
- return;
- }
- }
-}
-
-// Ends current read and calls delegate.
-- (void)completeCurrentRead
-{
- NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read.");
-
- [theCurrentRead->buffer setLength:theCurrentRead->bytesDone];
- if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)])
- {
- [theDelegate onSocket:self didReadData:theCurrentRead->buffer withTag:theCurrentRead->tag];
- }
-
- if (theCurrentRead != nil) [self endCurrentRead]; // Caller may have disconnected.
-}
-
-// Ends current read.
-- (void)endCurrentRead
-{
- NSAssert(theCurrentRead, @"Trying to end current read when there is no current read.");
-
- [theReadTimer invalidate];
- theReadTimer = nil;
-
- [theCurrentRead release];
- theCurrentRead = nil;
-}
-
-- (void)doReadTimeout:(NSTimer *)timer
-{
- NSTimeInterval timeoutExtension = 0.0;
-
- if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
- {
- timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag
- elapsed:theCurrentRead->timeout
- bytesDone:theCurrentRead->bytesDone];
- }
-
- if(timeoutExtension > 0.0)
- {
- theCurrentRead->timeout += timeoutExtension;
-
- theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension
- target:self
- selector:@selector(doReadTimeout:)
- userInfo:nil
- repeats:NO];
- [self runLoopAddTimer:theReadTimer];
- }
- else
- {
- // Do not call endCurrentRead here.
- // We must allow the delegate access to any partial read in the unreadData method.
-
- [self closeWithError:[self getReadTimeoutError]];
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Writing
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
-{
- if (data == nil || [data length] == 0) return;
- if (theFlags & kForbidReadsWrites) return;
-
- AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
-
- [theWriteQueue addObject:packet];
- [self scheduleDequeueWrite];
-
- [packet release];
-}
-
-- (void)scheduleDequeueWrite
-{
- if((theFlags & kDequeueWriteScheduled) == 0)
- {
- theFlags |= kDequeueWriteScheduled;
- [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- }
-}
-
-/**
- * Conditionally starts a new write.
- *
- * IF there is not another write in process
- * AND there is a write queued
- * AND we have a write stream available
- *
- * This method also handles auto-disconnect post read/write completion.
-**/
-- (void)maybeDequeueWrite
-{
- // Unset the flag indicating a call to this method is scheduled
- theFlags &= ~kDequeueWriteScheduled;
-
- // If we're not currently processing a write AND we have an available write stream
- if((theCurrentWrite == nil) && (theWriteStream != NULL))
- {
- if([theWriteQueue count] > 0)
- {
- // Dequeue the next object in the write queue
- theCurrentWrite = [[theWriteQueue objectAtIndex:0] retain];
- [theWriteQueue removeObjectAtIndex:0];
-
- if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]])
- {
- // Attempt to start TLS
- theFlags |= kStartingWriteTLS;
-
- // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set
- [self maybeStartTLS];
- }
- else
- {
- // Start time-out timer
- if(theCurrentWrite->timeout >= 0.0)
- {
- theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout
- target:self
- selector:@selector(doWriteTimeout:)
- userInfo:nil
- repeats:NO];
- [self runLoopAddTimer:theWriteTimer];
- }
-
- // Immediately write, if possible
- [self doSendBytes];
- }
- }
- else if(theFlags & kDisconnectAfterWrites)
- {
- if(theFlags & kDisconnectAfterReads)
- {
- if(([theReadQueue count] == 0) && (theCurrentRead == nil))
- {
- [self disconnect];
- }
- }
- else
- {
- [self disconnect];
- }
- }
- }
-}
-
-/**
- * Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes().
- * This method supports the kSocketCanAcceptBytes flag.
-**/
-- (BOOL)canAcceptBytes
-{
- if (theFlags & kSocketCanAcceptBytes)
- {
- return YES;
- }
- else
- {
- return CFWriteStreamCanAcceptBytes(theWriteStream);
- }
-}
-
-- (void)doSendBytes
-{
- if((theCurrentWrite != nil) && (theWriteStream != NULL))
- {
- // Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet)
-
- CFIndex totalBytesWritten = 0;
-
- BOOL done = NO;
- BOOL error = NO;
-
- while (!done && !error && [self canAcceptBytes])
- {
- // Figure out what to write.
- CFIndex bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone;
- CFIndex bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE;
- UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone);
-
- // Write.
- CFIndex bytesWritten = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite);
-
- // Unset the "can accept bytes" flag
- theFlags &= ~kSocketCanAcceptBytes;
-
- // Check results
- if (bytesWritten < 0)
- {
- error = YES;
- }
- else
- {
- // Update total amount read for the current write
- theCurrentWrite->bytesDone += bytesWritten;
-
- // Update total amount written in this method invocation
- totalBytesWritten += bytesWritten;
-
- // Is packet done?
- done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone);
- }
- }
-
- if(done)
- {
- [self completeCurrentWrite];
- [self scheduleDequeueWrite];
- }
- else if(error)
- {
- CFStreamError err = CFWriteStreamGetError(theWriteStream);
- [self closeWithError:[self errorFromCFStreamError:err]];
- return;
- }
- else
- {
- // We're not done with the entire write, but we have written some bytes
- if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)])
- {
- [theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag];
- }
- }
- }
-}
-
-// Ends current write and calls delegate.
-- (void)completeCurrentWrite
-{
- NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write.");
-
- if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)])
- {
- [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag];
- }
-
- if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected.
-}
-
-// Ends current write.
-- (void)endCurrentWrite
-{
- NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write.");
-
- [theWriteTimer invalidate];
- theWriteTimer = nil;
-
- [theCurrentWrite release];
- theCurrentWrite = nil;
-}
-
-- (void)doWriteTimeout:(NSTimer *)timer
-{
- NSTimeInterval timeoutExtension = 0.0;
-
- if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
- {
- timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag
- elapsed:theCurrentWrite->timeout
- bytesDone:theCurrentWrite->bytesDone];
- }
-
- if(timeoutExtension > 0.0)
- {
- theCurrentWrite->timeout += timeoutExtension;
-
- theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension
- target:self
- selector:@selector(doWriteTimeout:)
- userInfo:nil
- repeats:NO];
- [self runLoopAddTimer:theWriteTimer];
- }
- else
- {
- [self closeWithError:[self getWriteTimeoutError]];
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Security
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (void)startTLS:(NSDictionary *)tlsSettings
-{
- if(tlsSettings == nil)
- {
- // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
- // but causes problems if we later try to fetch the remote host's certificate.
- //
- // To be exact, it causes the following to return NULL instead of the normal result:
- // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
- //
- // So we use an empty dictionary instead, which works perfectly.
-
- tlsSettings = [NSDictionary dictionary];
- }
-
- AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
-
- [theReadQueue addObject:packet];
- [self scheduleDequeueRead];
-
- [theWriteQueue addObject:packet];
- [self scheduleDequeueWrite];
-
- [packet release];
-}
-
-- (void)maybeStartTLS
-{
- // We can't start TLS until:
- // - All queued reads prior to the user calling StartTLS are complete
- // - All queued writes prior to the user calling StartTLS are complete
- //
- // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
-
- if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS))
- {
- AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead;
-
- BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings,
- (CFDictionaryRef)tlsPacket->tlsSettings);
- BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings,
- (CFDictionaryRef)tlsPacket->tlsSettings);
-
- if(!didStartOnReadStream || !didStartOnWriteStream)
- {
- [self closeWithError:[self getSocketError]];
- }
- }
-}
-
-- (void)onTLSHandshakeSuccessful
-{
- if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS))
- {
- theFlags &= ~kStartingReadTLS;
- theFlags &= ~kStartingWriteTLS;
-
- if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)])
- {
- [theDelegate onSocketDidSecure:self];
- }
-
- [self endCurrentRead];
- [self endCurrentWrite];
-
- [self scheduleDequeueRead];
- [self scheduleDequeueWrite];
- }
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark CF Callbacks
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-- (void)doCFSocketCallback:(CFSocketCallBackType)type
- forSocket:(CFSocketRef)sock
- withAddress:(NSData *)address
- withData:(const void *)pData
-{
- NSParameterAssert ((sock == theSocket4) || (sock == theSocket6));
-
- switch (type)
- {
- case kCFSocketConnectCallBack:
- // The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed.
- if(pData)
- [self doSocketOpen:sock withCFSocketError:kCFSocketError];
- else
- [self doSocketOpen:sock withCFSocketError:kCFSocketSuccess];
- break;
- case kCFSocketAcceptCallBack:
- [self doAcceptWithSocket: *((CFSocketNativeHandle *)pData)];
- break;
- default:
- NSLog (@"AsyncSocket %p received unexpected CFSocketCallBackType %d.", self, type);
- break;
- }
-}
-
-- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream
-{
- NSParameterAssert(theReadStream != NULL);
-
- CFStreamError err;
- switch (type)
- {
- case kCFStreamEventOpenCompleted:
- theFlags |= kDidCompleteOpenForRead;
- [self doStreamOpen];
- break;
- case kCFStreamEventHasBytesAvailable:
- if(theFlags & kStartingReadTLS) {
- [self onTLSHandshakeSuccessful];
- }
- else {
- theFlags |= kSocketHasBytesAvailable;
- [self doBytesAvailable];
- }
- break;
- case kCFStreamEventErrorOccurred:
- case kCFStreamEventEndEncountered:
- err = CFReadStreamGetError (theReadStream);
- [self closeWithError: [self errorFromCFStreamError:err]];
- break;
- default:
- NSLog (@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %d.", self, type);
- }
-}
-
-- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream
-{
- NSParameterAssert(theWriteStream != NULL);
-
- CFStreamError err;
- switch (type)
- {
- case kCFStreamEventOpenCompleted:
- theFlags |= kDidCompleteOpenForWrite;
- [self doStreamOpen];
- break;
- case kCFStreamEventCanAcceptBytes:
- if(theFlags & kStartingWriteTLS) {
- [self onTLSHandshakeSuccessful];
- }
- else {
- theFlags |= kSocketCanAcceptBytes;
- [self doSendBytes];
- }
- break;
- case kCFStreamEventErrorOccurred:
- case kCFStreamEventEndEncountered:
- err = CFWriteStreamGetError (theWriteStream);
- [self closeWithError: [self errorFromCFStreamError:err]];
- break;
- default:
- NSLog (@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %d.", self, type);
- }
-}
-
-/**
- * This is the callback we setup for CFSocket.
- * This method does nothing but forward the call to it's Objective-C counterpart
-**/
-static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo)
-{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
- AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease];
- [theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData];
-
- [pool release];
-}
-
-/**
- * This is the callback we setup for CFReadStream.
- * This method does nothing but forward the call to it's Objective-C counterpart
-**/
-static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
-{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
- AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease];
- [theSocket doCFReadStreamCallback:type forStream:stream];
-
- [pool release];
-}
-
-/**
- * This is the callback we setup for CFWriteStream.
- * This method does nothing but forward the call to it's Objective-C counterpart
-**/
-static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
-{
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
- AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease];
- [theSocket doCFWriteStreamCallback:type forStream:stream];
-
- [pool release];
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark Class Methods
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// Return line separators.
-+ (NSData *)CRLFData
-{
- return [NSData dataWithBytes:"\x0D\x0A" length:2];
-}
-
-+ (NSData *)CRData
-{
- return [NSData dataWithBytes:"\x0D" length:1];
-}
-
-+ (NSData *)LFData
-{
- return [NSData dataWithBytes:"\x0A" length:1];
-}
-
-+ (NSData *)ZeroData
-{
- return [NSData dataWithBytes:"" length:1];
-}
-
-@end