mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
* initial commit * initial commit * add port files * ios: Changes to allow creating app bundle without xcode projects * ios: Added mic perms request * ios: Very basic app bundle creation script * ios: Fix -log parameter * ios: Sdk no longer hardcoded and option to compile for simulator * general: Update gitignore * ios: Enable game mode for the app * ios: Compile instructions and updated ipa creation script * ios: Code cleanup * ios: Remove dylibs * general: Remove extra submodules * ios: Remove dylibs again * ios: Implement suggested changes * ios: Fix building as dylib instead of binary * ios: Scripts for building mods * ios: Move 'ios' contents to engine/platform/ios/bundle and modify scripts * ios: Update createipa.sh to use waf install * ios: Clean script and updated README * general: Fix spelling mistake and outdated instructions * ios: Clean up in xcompile.py * ios: Fix createipa.sh generating an invalid ipa * ios: Temporary fix for launch dialog textfield colors in dark mode
2427 lines
73 KiB
Objective-C
2427 lines
73 KiB
Objective-C
//
|
|
// 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 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];
|
|
|
|
// 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 = 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;
|
|
}
|
|
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];
|
|
[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;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#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
|
|
{
|
|
// Get the CFRunLoop to which the socket should be attached.
|
|
theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop];
|
|
|
|
if(theSocket)
|
|
{
|
|
theSource = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket, 0);
|
|
CFRunLoopAddSource (theRunLoop, theSource, kCFRunLoopDefaultMode);
|
|
}
|
|
|
|
if(theSocket6)
|
|
{
|
|
theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0);
|
|
CFRunLoopAddSource (theRunLoop, theSource6, kCFRunLoopDefaultMode);
|
|
}
|
|
|
|
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
|
|
{
|
|
AsyncSocket *newSocket = [[[[self class] alloc] initWithDelegate:theDelegate] autorelease];
|
|
|
|
// Note: We use [self class] to support subclassing AsyncSocket.
|
|
|
|
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
|
|
{
|
|
// 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;
|
|
}
|
|
CFReadStreamScheduleWithRunLoop (theReadStream, theRunLoop, kCFRunLoopDefaultMode);
|
|
|
|
// 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;
|
|
|
|
}
|
|
CFWriteStreamScheduleWithRunLoop (theWriteStream, theRunLoop, kCFRunLoopDefaultMode);
|
|
|
|
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
|
|
{
|
|
// Empty queues.
|
|
[self emptyQueues];
|
|
[partialReadBuffer release];
|
|
partialReadBuffer = nil;
|
|
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil];
|
|
|
|
// Close streams.
|
|
if (theReadStream != NULL)
|
|
{
|
|
CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
|
|
CFReadStreamUnscheduleFromRunLoop (theReadStream, theRunLoop, kCFRunLoopDefaultMode);
|
|
CFReadStreamClose (theReadStream);
|
|
CFRelease (theReadStream);
|
|
theReadStream = NULL;
|
|
}
|
|
if (theWriteStream != NULL)
|
|
{
|
|
CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
|
|
CFWriteStreamUnscheduleFromRunLoop (theWriteStream, theRunLoop, kCFRunLoopDefaultMode);
|
|
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)
|
|
{
|
|
CFRunLoopRemoveSource (theRunLoop, theSource, kCFRunLoopDefaultMode);
|
|
CFRelease (theSource);
|
|
theSource = NULL;
|
|
}
|
|
if (theSource6 != NULL)
|
|
{
|
|
CFRunLoopRemoveSource (theRunLoop, theSource6, kCFRunLoopDefaultMode);
|
|
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];
|
|
}
|
|
}
|
|
|
|
// Clear flags.
|
|
theFlags = 0x00;
|
|
}
|
|
|
|
/**
|
|
* 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) ];
|
|
char addrBuf[46];
|
|
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];
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
#if 1
|
|
#define BUFFERSIZE (1024*1024) // the default buffer chunk size
|
|
NSMutableData *buffer = [[NSMutableData alloc] initWithLength:BUFFERSIZE]; // buffer
|
|
while ( theReadStream && CFReadStreamHasBytesAvailable(theReadStream) ){
|
|
// read 1K and call didReadData
|
|
CFIndex readSize = CFReadStreamRead( theReadStream, (UInt8*)[buffer bytes], BUFFERSIZE );
|
|
if ( readSize > 0 ){
|
|
if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)])
|
|
{
|
|
// when delegate is set, pass the current data(up to 1k size) to it.
|
|
|
|
NSData *buffer_;
|
|
if ( readSize == BUFFERSIZE ){
|
|
// when the buffer size is same as the max size, doesn't create a NSData instance.
|
|
buffer_ = buffer;
|
|
}
|
|
else{
|
|
// create a NSData instance with readSize.
|
|
buffer_ = [[NSData alloc] initWithBytesNoCopy: (void*)[buffer bytes] length: readSize freeWhenDone:NO];
|
|
}
|
|
[theDelegate onSocket:self didReadData: buffer_ withTag:theCurrentRead->tag];
|
|
if ( readSize < BUFFERSIZE ){
|
|
[buffer_ release];
|
|
}
|
|
|
|
}
|
|
}
|
|
if ( readSize <= 0 ){
|
|
break;
|
|
}
|
|
}
|
|
[buffer release];
|
|
#else
|
|
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;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// 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];
|
|
}
|
|
|
|
// 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];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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 kCFStreamEventEndEncountered:
|
|
if([theDelegate respondsToSelector:@selector(onReadStreamEnded:)]){
|
|
if([theDelegate onReadStreamEnded:self] == NO) break;
|
|
}
|
|
case kCFStreamEventErrorOccurred:
|
|
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
|