OSDN Git Service

Adding the ability to move a socket to a different thread/runloop.
[syncr/master.git] / CocoaAsyncSocket / CocoaAsyncSocket-5.1.1 / AsyncSocket.m
diff --git a/CocoaAsyncSocket/CocoaAsyncSocket-5.1.1/AsyncSocket.m b/CocoaAsyncSocket/CocoaAsyncSocket-5.1.1/AsyncSocket.m
new file mode 100644 (file)
index 0000000..f18584d
--- /dev/null
@@ -0,0 +1,2596 @@
+//
+//  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";
+
+// This is a mutex lock used by all instances of AsyncSocket, to protect getaddrinfo.
+// The man page says it is not thread-safe. (As of Mac OS X 10.4.7, and possibly earlier)
+static NSString *getaddrinfoLock = @"lock";
+
+enum AsyncSocketFlags
+{
+       kEnablePreBuffering   = 1 << 0,   // If set, pre-buffering is enabled.
+       kDidCallConnectDeleg  = 1 << 1,   // If set, connect delegate has been called.
+       kDidPassConnectMethod = 1 << 2,   // If set, disconnection results in delegate call.
+       kForbidReadsWrites    = 1 << 3,   // If set, no new reads or writes are allowed.
+       kDisconnectSoon       = 1 << 4,   // If set, disconnect as soon as nothing is queued.
+       kClosingWithError     = 1 << 5,   // If set, the socket is being closed due to an error.
+};
+
+@interface AsyncSocket (Private)
+
+// Socket Implementation
+- (CFSocketRef) createAcceptSocketForAddress:(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 *) 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;
+
+// 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 a current 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 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;
+
+- (void)dealloc;
+@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 -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface AsyncWritePacket : NSObject
+{
+@public
+       NSData *buffer;
+       CFIndex bytesDone;
+       long tag;
+       NSTimeInterval timeout;
+}
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+- (void)dealloc;
+@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 -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@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 : 0x00;
+               theDelegate = delegate;
+               theUserData = userData;
+               
+               theSocket = NULL;
+               theSource = NULL;
+               theSocket6 = NULL;
+               theSource6 = NULL;
+               theRunLoop = NULL;
+               theReadStream = NULL;
+               theWriteStream = NULL;
+               
+               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];
+       [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onSocketDidDisconnect:) object:self];
+       [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(theSocket)
+               return theSocket;
+       else
+               return theSocket6;
+}
+
+- (CFReadStreamRef)getCFReadStream
+{
+       return theReadStream;
+}
+
+- (CFWriteStreamRef)getCFWriteStream
+{
+       return theWriteStream;
+}
+
+- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
+{
+       // Check to make sure we're actually reading something right now
+       if (!theCurrentRead) 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.0 : ratio; // 0 of 0 bytes is 100% done.
+}
+
+- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
+{
+       if (!theCurrentWrite) 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 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 == CFRunLoopGetCurrent()), @"moveToRunLoop must be called from within the current RunLoop!");
+       
+       if(theRunLoop == [runLoop getCFRunLoop])
+       {
+               return YES;
+       }
+       
+       int i;
+       
+       [NSObject cancelPreviousPerformRequestsWithTarget:self];
+       
+       if(theReadStream && theWriteStream)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       
+                       CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode);
+                       CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode);
+               }
+               CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
+               CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
+       }
+       if(theSource)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource, runLoopMode);
+               }
+       }
+       if(theSource6)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource6, runLoopMode);
+               }
+       }
+       
+       theRunLoop = [runLoop getCFRunLoop];
+       
+       if(theSource != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource(theRunLoop, theSource, runLoopMode);
+               }
+       }
+       if(theSource6 != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource(theRunLoop, theSource6, runLoopMode);
+               }
+       }
+       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
+{
+       if([theRunLoopModes isEqualToArray:runLoopModes])
+       {
+               return YES;
+       }
+       
+       int i;
+       
+       [NSObject cancelPreviousPerformRequestsWithTarget:self];
+       
+       if(theReadStream && theWriteStream)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       
+                       CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode);
+                       CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode);
+               }
+               CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
+               CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
+       }
+       if(theSource)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource, runLoopMode);
+               }
+       }
+       if(theSource6)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource6, runLoopMode);
+               }
+       }
+       
+       [theRunLoopModes release];
+       theRunLoopModes = [runLoopModes copy];
+       
+       if(theSource != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource(theRunLoop, theSource, runLoopMode);
+               }
+       }
+       if(theSource6 != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource(theRunLoop, theSource6, runLoopMode);
+               }
+       }
+       if(theReadStream && theWriteStream)
+       {
+               if(![self attachStreamsToRunLoop:(NSRunLoop *)theRunLoop 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connection
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr
+{
+       return [self acceptOnAddress:nil port:port error:errPtr];
+}
+       
+/**
+ * To accept on a certain address, pass the address to accept on.
+ * To accept on any address, pass nil or an empty string.
+ * To accept only connections from localhost pass "localhost" or "loopback".
+**/
+- (BOOL)acceptOnAddress:(NSString *)hostaddr port:(UInt16)port error:(NSError **)errPtr
+{
+       if (theDelegate == NULL)
+               [NSException raise:AsyncSocketException format:@"Attempting to accept without a delegate. Set a delegate first."];
+       
+       if (theSocket != 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 *address = nil, *address6 = nil;
+       if(hostaddr == nil || ([hostaddr length] == 0))
+       {
+               // Accept on ANY address
+               struct sockaddr_in nativeAddr;
+               nativeAddr.sin_len         = sizeof(struct sockaddr_in);
+               nativeAddr.sin_family      = AF_INET;
+               nativeAddr.sin_port        = htons(port);
+               nativeAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+               memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+               
+               struct sockaddr_in6 nativeAddr6;
+               nativeAddr6.sin6_len       = sizeof(struct sockaddr_in6);
+               nativeAddr6.sin6_family    = AF_INET6;
+               nativeAddr6.sin6_port      = htons(port);
+               nativeAddr6.sin6_flowinfo  = 0;
+               nativeAddr6.sin6_addr      = in6addr_any;
+               nativeAddr6.sin6_scope_id  = 0;
+               
+               // Wrap the native address structures for CFSocketSetAddress.
+               address = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+               address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+       }
+       else if([hostaddr isEqualToString:@"localhost"] || [hostaddr isEqualToString:@"loopback"])
+       {
+               // Accept only on LOOPBACK address
+               struct sockaddr_in nativeAddr;
+               nativeAddr.sin_len         = sizeof(struct sockaddr_in);
+               nativeAddr.sin_family      = AF_INET;
+               nativeAddr.sin_port        = htons(port);
+               nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+               memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+       
+               struct sockaddr_in6 nativeAddr6;
+               nativeAddr6.sin6_len       = sizeof(struct sockaddr_in6);
+               nativeAddr6.sin6_family    = AF_INET6;
+               nativeAddr6.sin6_port      = htons(port);
+               nativeAddr6.sin6_flowinfo  = 0;
+               nativeAddr6.sin6_addr      = in6addr_loopback;
+               nativeAddr6.sin6_scope_id  = 0;
+               
+               // Wrap the native address structures for CFSocketSetAddress.
+               address = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+               address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+       }
+       else
+       {
+               NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+               
+               @synchronized (getaddrinfoLock)
+               {
+                       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([hostaddr 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(!address && (res->ai_family == AF_INET))
+                               {
+                                       // Found IPv4 address
+                                       // Wrap the native address structures for CFSocketSetAddress.
+                                       address = [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(!address && !address6) return NO;
+       }
+
+       // Create the sockets.
+
+       if (address)
+       {
+               theSocket = [self createAcceptSocketForAddress:address error:errPtr];
+               if (theSocket == NULL) goto Failed;
+       }
+       
+       if (address6)
+       {
+               theSocket6 = [self createAcceptSocketForAddress: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 (theSocket)  setsockopt(CFSocketGetNative(theSocket), 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 (theSocket)
+       {
+               err = CFSocketSetAddress (theSocket, (CFDataRef)address);
+               if (err != kCFSocketSuccess) goto Failed;
+               
+               //NSLog(@"theSocket4: %hu", [self localPort:theSocket]);
+       }
+       
+       if(port == 0 && theSocket && 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 theSocket and theSocket6
+               // So we grab the port the kernel choose for theSocket, and set it as the port for theSocket6
+               UInt16 chosenPort = [self localPort:theSocket];
+               
+               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(theSocket != NULL)
+       {
+               CFSocketInvalidate(theSocket);
+               CFRelease(theSocket);
+               theSocket = NULL;
+       }
+       if(theSocket6 != NULL)
+       {
+               CFSocketInvalidate(theSocket6);
+               CFRelease(theSocket6);
+               theSocket6 = NULL;
+       }
+       return NO;
+}
+
+/**
+ * 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 error:(NSError **)errPtr
+{
+       if(theDelegate == NULL)
+       {
+               NSString *message = @"Attempting to connect without a delegate. Set a delegate first.";
+               [NSException raise:AsyncSocketException format:message];
+       }
+
+       if(theSocket != NULL || theSocket6 != NULL)
+       {
+               NSString *message = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+               [NSException raise:AsyncSocketException format:message];
+       }
+       
+       BOOL pass = YES;
+       
+       if(pass && ![self createStreamsToHost:hostname onPort:port error:errPtr]) pass = NO;
+       if(pass && ![self attachStreamsToRunLoop:nil error:errPtr])               pass = NO;
+       if(pass && ![self configureStreamsAndReturnError:errPtr])                 pass = NO;
+       if(pass && ![self openStreamsAndReturnError:errPtr])                      pass = NO;
+       
+       if(pass)
+               theFlags |= kDidPassConnectMethod;
+       else
+               [self close];
+       
+       return pass;
+}
+
+/**
+ * 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 error:(NSError **)errPtr
+{
+       if (theDelegate == NULL)
+       {
+               NSString *message = @"Attempting to connect without a delegate. Set a delegate first.";
+               [NSException raise:AsyncSocketException format:message];
+       }
+       
+       if (theSocket != NULL || theSocket6 != NULL)
+       {
+               NSString *message = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+               [NSException raise:AsyncSocketException format:message];
+       }
+       
+       BOOL pass = YES;
+       
+       if(pass && ![self createSocketForAddress:remoteAddr error:errPtr])   pass = NO;
+       if(pass && ![self attachSocketsToRunLoop:nil error:errPtr])          pass = NO;
+       if(pass && ![self configureSocketAndReturnError:errPtr])             pass = NO;
+       if(pass && ![self connectSocketToAddress:remoteAddr error:errPtr])   pass = NO;
+       
+       if(pass)
+               theFlags |= kDidPassConnectMethod;
+       else
+               [self close];
+       
+       return pass;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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)createAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr
+{
+       struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes];
+       int addressFamily = pSockAddr->sa_family;
+       
+       CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault,
+                                                                               addressFamily,
+                                                                               SOCK_STREAM,
+                                                                               0,
+                                                                               kCFSocketAcceptCallBack,                // Callback flags
+                                                                               (CFSocketCallBack)&MyCFSocketCallback,  // Callback method
+                                                                               &theContext);
+
+       if(socket == NULL)
+       {
+               if(errPtr) *errPtr = [self getSocketError];
+       }
+       
+       return socket;
+}
+
+- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+       struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes];
+       
+       if(pSockAddr->sa_family == AF_INET)
+       {
+               theSocket = 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(theSocket == 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
+{
+       int i;
+       
+       // Get the CFRunLoop to which the socket should be attached.
+       theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop];
+       
+       if(theSocket)
+       {
+               theSource  = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket, 0);
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource (theRunLoop, theSource, runLoopMode);
+               }
+       }
+       
+       if(theSocket6)
+       {
+               theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0);
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopAddSource (theRunLoop, theSource6, runLoopMode);
+               }
+       }
+       
+       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(theSocket)
+       {
+               CFSocketError err = CFSocketConnectToAddress(theSocket, (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 == theSocket) || (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);
+       theSocket = 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
+{
+       int i;
+       
+       // Get the CFRunLoop to which the socket should be attached.
+       theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop];
+
+       // Make read stream non-blocking.
+       if (!CFReadStreamSetClient (theReadStream,
+               kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered | kCFStreamEventOpenCompleted,
+               (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;
+       }
+       for(i = 0; i < [theRunLoopModes count]; i++)
+       {
+               CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, (CFStringRef)[theRunLoopModes objectAtIndex:i]);
+       }
+
+       // Make write stream non-blocking.
+       if (!CFWriteStreamSetClient (theWriteStream,
+               kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered | kCFStreamEventOpenCompleted,
+               (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;
+               
+       }
+       for(i = 0; i < [theRunLoopModes count]; i++)
+       {
+               CFWriteStreamScheduleWithRunLoop (theWriteStream, theRunLoop, (CFStringRef)[theRunLoopModes objectAtIndex:i]);
+       }
+       
+       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 ([self areStreamsConnected] && !(theFlags & kDidCallConnectDeleg))
+       {
+               // Get the socket.
+               if (![self setSocketFromStreamsAndReturnError: &err])
+               {
+                       NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err);
+                       [self closeWithError:err];
+                       return;
+               }
+               
+               // Call the delegate.
+               theFlags |= kDidCallConnectDeleg;
+               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 socket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL);
+       if(socket == NULL)
+       {
+               if (errPtr) *errPtr = [self getSocketError];
+               return NO;
+       }
+       
+       // Determine whether the connection was IPv4 or IPv6
+       CFDataRef peeraddr = CFSocketCopyPeerAddress(socket);
+       struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr);
+       
+       if(sa->sa_family == AF_INET)
+       {
+               theSocket = socket;
+       }
+       else
+       {
+               theSocket6 = socket;
+       }
+       
+       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) && (theCurrentRead->bytesDone > 0))
+       {
+               // We never finished the current read.
+               // 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];
+}
+
+// Disconnects. This is called for both error and clean disconnections.
+- (void)close
+{
+       int i;
+       
+       // 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];
+       
+       // Close streams.
+       if (theReadStream != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode);
+               }
+               CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
+               CFReadStreamClose(theReadStream);
+               CFRelease(theReadStream);
+               theReadStream = NULL;
+       }
+       if (theWriteStream != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode);
+               }
+               CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
+               CFWriteStreamClose(theWriteStream);
+               CFRelease(theWriteStream);
+               theWriteStream = NULL;
+       }
+       
+       // Close sockets.
+       if (theSocket != NULL)
+       {
+               CFSocketInvalidate (theSocket);
+               CFRelease (theSocket);
+               theSocket = NULL;
+       }
+       if (theSocket6 != NULL)
+       {
+               CFSocketInvalidate (theSocket6);
+               CFRelease (theSocket6);
+               theSocket6 = NULL;
+       }
+       if (theSource != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource, runLoopMode);
+               }
+               CFRelease (theSource);
+               theSource = NULL;
+       }
+       if (theSource6 != NULL)
+       {
+               for(i = 0; i < [theRunLoopModes count]; i++)
+               {
+                       CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+                       CFRunLoopRemoveSource(theRunLoop, theSource6, runLoopMode);
+               }
+               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.
+       if (theFlags & kDidPassConnectMethod)
+       {
+               // Delay notification to give him freedom to release without returning here and core-dumping.
+               if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)])
+               {
+                       [theDelegate performSelector:@selector(onSocketDidDisconnect:)
+                                                         withObject:self
+                                                         afterDelay:0
+                                                                inModes:theRunLoopModes];
+               }
+       }
+       
+       // Clear all flags (except the pre-buffering flag, which should remain as is)
+       theFlags &= kEnablePreBuffering;
+}
+
+/**
+ * Disconnects immediately. Any pending reads or writes are dropped.
+**/
+- (void)disconnect
+{
+       [self close];
+}
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods (including "readDataWithTimeout:tag:") will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting
+{
+       theFlags |= kForbidReadsWrites;
+       theFlags |= kDisconnectSoon;
+       [self maybeScheduleDisconnect];
+}
+
+/**
+ * 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];
+}
+
+- (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(theSocket)
+               return [self connectedHost:theSocket];
+       else
+               return [self connectedHost:theSocket6];
+}
+
+- (UInt16)connectedPort
+{
+       if(theSocket)
+               return [self connectedPort:theSocket];
+       else
+               return [self connectedPort:theSocket6];
+}
+
+- (NSString *)localHost
+{
+       if(theSocket)
+               return [self localHost:theSocket];
+       else
+               return [self localHost:theSocket6];
+}
+
+- (UInt16)localPort
+{
+       if(theSocket)
+               return [self localPort:theSocket];
+       else
+               return [self localPort:theSocket6];
+}
+
+- (NSString *)connectedHost:(CFSocketRef)socket
+{
+       if (socket == NULL) return nil;
+       CFDataRef peeraddr;
+       NSString *peerstr = nil;
+
+       if(socket && (peeraddr = CFSocketCopyPeerAddress(socket)))
+       {
+               peerstr = [self addressHost:peeraddr];
+               CFRelease (peeraddr);
+       }
+
+       return peerstr;
+}
+
+- (UInt16)connectedPort:(CFSocketRef)socket
+{
+       if (socket == NULL) return 0;
+       CFDataRef peeraddr;
+       UInt16 peerport = 0;
+
+       if(socket && (peeraddr = CFSocketCopyPeerAddress(socket)))
+       {
+               peerport = [self addressPort:peeraddr];
+               CFRelease (peeraddr);
+       }
+
+       return peerport;
+}
+
+- (NSString *)localHost:(CFSocketRef)socket
+{
+       if (socket == NULL) return nil;
+       CFDataRef selfaddr;
+       NSString *selfstr = nil;
+
+       if(socket && (selfaddr = CFSocketCopyAddress(socket)))
+       {
+               selfstr = [self addressHost:selfaddr];
+               CFRelease (selfaddr);
+       }
+
+       return selfstr;
+}
+
+- (UInt16)localPort:(CFSocketRef)socket
+{
+       if (socket == NULL) return 0;
+       CFDataRef selfaddr;
+       UInt16 selfport = 0;
+
+       if (socket && (selfaddr = CFSocketCopyAddress(socket)))
+       {
+               selfport = [self addressPort:selfaddr];
+               CFRelease (selfaddr);
+       }
+
+       return selfport;
+}
+
+- (BOOL)isSocketConnected
+{
+       if(theSocket != NULL)
+               return CFSocketIsValid(theSocket);
+       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 *)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);
+}
+
+- (BOOL)isIPv4
+{
+       return (theSocket != NULL);
+}
+
+- (BOOL)isIPv6
+{
+       return (theSocket6 != NULL);
+}
+
+- (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 peeraddr = NULL, peeraddr6 = NULL, selfaddr = NULL, selfaddr6 = NULL;
+
+       if (theSocket || theSocket6)
+       {
+               if (theSocket)  peeraddr  = CFSocketCopyPeerAddress(theSocket);
+               if (theSocket6) peeraddr6 = CFSocketCopyPeerAddress(theSocket6);
+       
+               if(theSocket6 && theSocket)
+               {
+                       peerstr = [NSString stringWithFormat: @"%@/%@ %u", 
+                                          [self addressHost:peeraddr], [self addressHost:peeraddr6], [self addressPort:peeraddr]];
+               }
+               else if(theSocket6)
+               {
+                       peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr6], [self addressPort:peeraddr6]];
+               }
+               else
+               {
+                       peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr], [self addressPort:peeraddr]];
+               }
+               
+               if(peeraddr)  CFRelease(peeraddr);
+               if(peeraddr6) CFRelease(peeraddr6);
+               peeraddr = NULL;
+               peeraddr6 = NULL;
+       }
+       else peerstr = @"nowhere";
+
+       if (theSocket || theSocket6)
+       {
+               if (theSocket)  selfaddr  = CFSocketCopyAddress (theSocket);
+               if (theSocket6) selfaddr6 = CFSocketCopyAddress (theSocket6);
+       
+               if (theSocket6 && theSocket)
+               {
+                       selfstr = [NSString stringWithFormat: @"%@/%@ %u",
+                                          [self addressHost:selfaddr], [self addressHost:selfaddr6], [self addressPort:selfaddr]];
+               }
+               else if (theSocket6)
+               {
+                       selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr6], [self addressPort:selfaddr6]];
+               }
+               else
+               {
+                       selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr], [self addressPort:selfaddr]];
+               }
+
+               if(selfaddr)  CFRelease(selfaddr);
+               if(selfaddr6) CFRelease(selfaddr6);
+               selfaddr = NULL;
+               selfaddr6 = NULL;
+       }
+       else selfstr = @"nowhere";
+       
+       NSMutableString *ms = [[NSMutableString alloc] init];
+       [ms appendString: [NSString stringWithFormat:@"<AsyncSocket %p", self]];
+       [ms appendString: [NSString stringWithFormat:@" local %@ remote %@ ", selfstr, peerstr]];
+       [ms appendString: [NSString stringWithFormat:@"has queued %d reads %d writes, ", [theReadQueue count], [theWriteQueue count] ]];
+
+       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.0;
+               else
+                       percentDone = 100;
+
+               [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ",
+                       [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.0;
+               else
+                       percentDone = 100;
+
+               [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ",
+                       [theCurrentWrite->buffer length],
+                       theCurrentWrite->bytesDone ? percentDone : 0]];
+       }
+       
+       [ms appendString: [NSString stringWithFormat:@"read stream %p %s, write stream %p %s", theReadStream, statstr [rs], theWriteStream, statstr [ws] ]];
+       if (theFlags & kDisconnectSoon) [ms appendString: @", will disconnect soon"];
+       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
+{
+       [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
+{
+       // If we're not currently processing a read AND
+       // we have read requests sitting in the queue AND we have actually have a read stream
+       if(theCurrentRead == nil && [theReadQueue count] != 0 && theReadStream != NULL)
+       {
+               // Get new current read AsyncReadPacket.
+               AsyncReadPacket *newPacket = [theReadQueue objectAtIndex:0];
+               theCurrentRead = [newPacket retain];
+               [theReadQueue removeObjectAtIndex:0];
+
+               // Start time-out timer.
+               if(theCurrentRead->timeout >= 0.0)
+               {
+                       theReadTimer = [NSTimer scheduledTimerWithTimeInterval:theCurrentRead->timeout
+                                                                                                                       target:self 
+                                                                                                                 selector:@selector(doReadTimeout:)
+                                                                                                                 userInfo:nil
+                                                                                                                  repeats:NO];
+               }
+
+               // Immediately read, if possible.
+               [self doBytesAvailable];
+       }
+}
+
+/**
+ * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable().
+ * This method supports pre-buffering properly.
+**/
+- (BOOL)hasBytesAvailable
+{
+       return ([partialReadBuffer length] > 0) || 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
+       {
+               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)
+       {
+               CFIndex totalBytesRead = 0;
+               
+               BOOL done = NO;
+               BOOL socketError = NO;
+               BOOL maxoutError = NO;
+               
+               while(!done && !socketError && !maxoutError && [self hasBytesAvailable])
+               {
+                       BOOL didPreBuffer = NO;
+                       
+                       // If reading all available data, make sure there's room in the packet buffer.
+                       if(theCurrentRead->readAllAvailableData == YES)
+                       {
+                               // 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];
+                       }
+
+                       // If reading until data, 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(theCurrentRead->term != nil)
+                       {
+                               // 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 amound 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)
+                       done = YES;     // Ran out of bytes, so the "read-all-data" type packet is done
+
+               if(done)
+               {
+                       [self completeCurrentRead];
+                       if (!socketError) [self scheduleDequeueRead];
+               }
+               else if(theCurrentRead->bytesDone > 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
+{
+       if (timer != theReadTimer) return; // Old timer. Ignore it.
+       if (theCurrentRead != nil)
+       {
+               [self endCurrentRead];
+       }
+       [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
+{
+       [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+}
+
+// Start a new write.
+- (void)maybeDequeueWrite
+{
+       if (theCurrentWrite == nil && [theWriteQueue count] != 0 && theWriteStream != NULL)
+       {
+               // Get new current write AsyncWritePacket.
+               AsyncWritePacket *newPacket = [theWriteQueue objectAtIndex:0];
+               theCurrentWrite = [newPacket retain];
+               [theWriteQueue removeObjectAtIndex:0];
+               
+               // Start time-out timer.
+               if (theCurrentWrite->timeout >= 0.0)
+               {
+                       theWriteTimer = [NSTimer scheduledTimerWithTimeInterval:theCurrentWrite->timeout
+                                                                        target:self
+                                                                      selector:@selector(doWriteTimeout:)
+                                                                      userInfo:nil
+                                                                       repeats:NO];
+               }
+
+               // Immediately write, if possible.
+               [self doSendBytes];
+       }
+}
+
+- (void)doSendBytes
+{
+       if (theCurrentWrite != nil && theWriteStream != NULL)
+       {
+               BOOL done = NO, error = NO;
+               while (!done && !error && CFWriteStreamCanAcceptBytes (theWriteStream))
+               {
+                       // 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);
+
+                       // Check results.
+                       if (bytesWritten < 0)
+                       {
+                               bytesWritten = 0;
+                               error = YES;
+                       }
+
+                       // Is packet done?
+                       theCurrentWrite->bytesDone += bytesWritten;
+                       done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone);
+               }
+
+               if(done)
+               {
+                       [self completeCurrentWrite];
+                       if (!error) [self scheduleDequeueWrite];
+               }
+
+               if(error)
+               {
+                       CFStreamError err = CFWriteStreamGetError (theWriteStream);
+                       [self closeWithError: [self errorFromCFStreamError:err]];
+                       return;
+               }
+       }
+}
+
+// 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;
+       
+       [self maybeScheduleDisconnect];
+}
+
+// Checks to see if all writes have been completed for disconnectAfterWriting.
+- (void)maybeScheduleDisconnect
+{
+       if(theFlags & kDisconnectSoon)
+       {
+               if(([theWriteQueue count] == 0) && (theCurrentWrite == nil))
+               {
+                       [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+               }
+       }
+}
+
+- (void)doWriteTimeout:(NSTimer *)timer
+{
+       if (timer != theWriteTimer) return; // Old timer. Ignore it.
+       if (theCurrentWrite != nil)
+       {
+               [self endCurrentWrite];
+       }
+       [self closeWithError:[self getWriteTimeoutError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CF Callbacks
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)doCFSocketCallback:(CFSocketCallBackType)type
+                                forSocket:(CFSocketRef)sock
+                          withAddress:(NSData *)address
+                                 withData:(const void *)pData
+{
+       NSParameterAssert ((sock == theSocket) || (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:
+                       [self doStreamOpen];
+                       break;
+               case kCFStreamEventHasBytesAvailable:
+                       [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:
+                       [self doStreamOpen];
+                       break;
+               case kCFStreamEventCanAcceptBytes:
+                       [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 *socket = [[(AsyncSocket *)pInfo retain] autorelease];
+       [socket 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 *socket = [[(AsyncSocket *)pInfo retain] autorelease];
+       [socket 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 *socket = [[(AsyncSocket *)pInfo retain] autorelease];
+       [socket 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