/ep-020/test_longpress_pinch/build/iphone/Classes/AsyncUdpSocket.m
Objective C | 2343 lines | 1699 code | 384 blank | 260 comment | 400 complexity | 5b4a27cd1c881c25c92bf6f595fff53b MD5 | raw file
Possible License(s): Apache-2.0
Large files files are truncated, but you can click here to view the full file
- //
- // AsyncUdpSocket.m
- //
- // This class is in the public domain.
- // Originally created by Robbie Hanson on Wed Oct 01 2008.
- // Updated and maintained by Deusty Designs and the Mac development community.
- //
- // http://code.google.com/p/cocoaasyncsocket/
- //
- #import "AsyncUdpSocket.h"
- #import <sys/socket.h>
- #import <netinet/in.h>
- #import <arpa/inet.h>
- #import <sys/ioctl.h>
- #import <net/if.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
- #define SENDQUEUE_CAPACITY 5 // Initial capacity
- #define RECEIVEQUEUE_CAPACITY 5 // Initial capacity
- #define DEFAULT_MAX_RECEIVE_BUFFER_SIZE 9216
- NSString *const AsyncUdpSocketException = @"AsyncUdpSocketException";
- NSString *const AsyncUdpSocketErrorDomain = @"AsyncUdpSocketErrorDomain";
- #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- // Mutex lock used by all instances of AsyncUdpSocket, to protect getaddrinfo.
- // Prior to Mac OS X 10.5 this method was not thread-safe.
- static NSString *getaddrinfoLock = @"lock";
- #endif
- enum AsyncUdpSocketFlags
- {
- kDidBind = 1 << 0, // If set, bind has been called.
- kDidConnect = 1 << 1, // If set, connect has been called.
- kSock4CanAcceptBytes = 1 << 2, // If set, we know socket4 can accept bytes. If unset, it's unknown.
- kSock6CanAcceptBytes = 1 << 3, // If set, we know socket6 can accept bytes. If unset, it's unknown.
- kSock4HasBytesAvailable = 1 << 4, // If set, we know socket4 has bytes available. If unset, it's unknown.
- kSock6HasBytesAvailable = 1 << 5, // If set, we know socket6 has bytes available. If unset, it's unknown.
- kForbidSendReceive = 1 << 6, // If set, no new send or receive operations are allowed to be queued.
- kCloseAfterSends = 1 << 7, // If set, close as soon as no more sends are queued.
- kCloseAfterReceives = 1 << 8, // If set, close as soon as no more receives are queued.
- kDidClose = 1 << 9, // If set, the socket has been closed, and should not be used anymore.
- kDequeueSendScheduled = 1 << 10, // If set, a maybeDequeueSend operation is already scheduled.
- kDequeueReceiveScheduled = 1 << 11, // If set, a maybeDequeueReceive operation is already scheduled.
- kFlipFlop = 1 << 12, // Used to alternate between IPv4 and IPv6 sockets.
- };
- @interface AsyncUdpSocket (Private)
- // Run Loop
- - (void)runLoopAddSource:(CFRunLoopSourceRef)source;
- - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source;
- - (void)runLoopAddTimer:(NSTimer *)timer;
- - (void)runLoopRemoveTimer:(NSTimer *)timer;
- // Utilities
- - (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4;
- - (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6;
- - (NSString *)addressHost:(struct sockaddr *)pSockaddr;
- // Disconnect Implementation
- - (void)emptyQueues;
- - (void)closeSocket4;
- - (void)closeSocket6;
- - (void)maybeScheduleClose;
- // Errors
- - (NSError *)getErrnoError;
- - (NSError *)getSocketError;
- - (NSError *)getIPv4UnavailableError;
- - (NSError *)getIPv6UnavailableError;
- - (NSError *)getSendTimeoutError;
- - (NSError *)getReceiveTimeoutError;
- // Diagnostics
- - (NSString *)connectedHost:(CFSocketRef)socket;
- - (UInt16)connectedPort:(CFSocketRef)socket;
- - (NSString *)localHost:(CFSocketRef)socket;
- - (UInt16)localPort:(CFSocketRef)socket;
- // Sending
- - (BOOL)canAcceptBytes:(CFSocketRef)sockRef;
- - (void)scheduleDequeueSend;
- - (void)maybeDequeueSend;
- - (void)doSend:(CFSocketRef)sockRef;
- - (void)completeCurrentSend;
- - (void)failCurrentSend:(NSError *)error;
- - (void)endCurrentSend;
- - (void)doSendTimeout:(NSTimer *)timer;
- // Receiving
- - (BOOL)hasBytesAvailable:(CFSocketRef)sockRef;
- - (void)scheduleDequeueReceive;
- - (void)maybeDequeueReceive;
- - (void)doReceive4;
- - (void)doReceive6;
- - (void)doReceive:(CFSocketRef)sockRef;
- - (BOOL)maybeCompleteCurrentReceive;
- - (void)failCurrentReceive:(NSError *)error;
- - (void)endCurrentReceive;
- - (void)doReceiveTimeout:(NSTimer *)timer;
- @end
- static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *);
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * The AsyncSendPacket encompasses the instructions for a single send/write.
- **/
- @interface AsyncSendPacket : NSObject
- {
- @public
- NSData *buffer;
- NSData *address;
- NSTimeInterval timeout;
- long tag;
- }
- - (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i;
- @end
- @implementation AsyncSendPacket
- - (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i
- {
- if((self = [super init]))
- {
- buffer = [d retain];
- address = [a retain];
- timeout = t;
- tag = i;
- }
- return self;
- }
- - (void)dealloc
- {
- [buffer release];
- [address release];
- [super dealloc];
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * The AsyncReceivePacket encompasses the instructions for a single receive/read.
- **/
- @interface AsyncReceivePacket : NSObject
- {
- @public
- NSTimeInterval timeout;
- long tag;
- NSMutableData *buffer;
- NSString *host;
- UInt16 port;
- }
- - (id)initWithTimeout:(NSTimeInterval)t tag:(long)i;
- @end
- @implementation AsyncReceivePacket
- - (id)initWithTimeout:(NSTimeInterval)t tag:(long)i
- {
- if((self = [super init]))
- {
- timeout = t;
- tag = i;
-
- buffer = nil;
- host = nil;
- port = 0;
- }
- return self;
- }
- - (void)dealloc
- {
- [buffer release];
- [host release];
- [super dealloc];
- }
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark -
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @implementation AsyncUdpSocket
- - (id)initWithDelegate:(id)delegate userData:(long)userData enableIPv4:(BOOL)enableIPv4 enableIPv6:(BOOL)enableIPv6
- {
- if((self = [super init]))
- {
- theFlags = 0;
- theDelegate = delegate;
- theUserData = userData;
- maxReceiveBufferSize = DEFAULT_MAX_RECEIVE_BUFFER_SIZE;
-
- theSendQueue = [[NSMutableArray alloc] initWithCapacity:SENDQUEUE_CAPACITY];
- theCurrentSend = nil;
- theSendTimer = nil;
-
- theReceiveQueue = [[NSMutableArray alloc] initWithCapacity:RECEIVEQUEUE_CAPACITY];
- theCurrentReceive = nil;
- theReceiveTimer = nil;
-
- // Socket context
- theContext.version = 0;
- theContext.info = self;
- theContext.retain = nil;
- theContext.release = nil;
- theContext.copyDescription = nil;
-
- // Create the sockets
- theSocket4 = NULL;
- theSocket6 = NULL;
-
- if(enableIPv4)
- {
- theSocket4 = CFSocketCreate(kCFAllocatorDefault,
- PF_INET,
- SOCK_DGRAM,
- IPPROTO_UDP,
- kCFSocketReadCallBack | kCFSocketWriteCallBack,
- (CFSocketCallBack)&MyCFSocketCallback,
- &theContext);
- }
- if(enableIPv6)
- {
- theSocket6 = CFSocketCreate(kCFAllocatorDefault,
- PF_INET6,
- SOCK_DGRAM,
- IPPROTO_UDP,
- kCFSocketReadCallBack | kCFSocketWriteCallBack,
- (CFSocketCallBack)&MyCFSocketCallback,
- &theContext);
- }
-
- // Disable continuous callbacks for read and write.
- // If we don't do this, the socket(s) will just sit there firing read callbacks
- // at us hundreds of times a second if we don't immediately read the available data.
- if(theSocket4)
- {
- CFSocketSetSocketFlags(theSocket4, kCFSocketCloseOnInvalidate);
- }
- if(theSocket6)
- {
- CFSocketSetSocketFlags(theSocket6, kCFSocketCloseOnInvalidate);
- }
-
- // Get the CFRunLoop to which the socket should be attached.
- theRunLoop = CFRunLoopGetCurrent();
-
- // Set default run loop modes
- theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain];
-
- // Attach the sockets to the run loop
-
- if(theSocket4)
- {
- theSource4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket4, 0);
- [self runLoopAddSource:theSource4];
- }
-
- if(theSocket6)
- {
- theSource6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket6, 0);
- [self runLoopAddSource:theSource6];
- }
-
- cachedLocalPort = 0;
- cachedConnectedPort = 0;
- }
- return self;
- }
- - (id)init
- {
- return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:YES];
- }
- - (id)initWithDelegate:(id)delegate
- {
- return [self initWithDelegate:delegate userData:0 enableIPv4:YES enableIPv6:YES];
- }
- - (id)initWithDelegate:(id)delegate userData:(long)userData
- {
- return [self initWithDelegate:delegate userData:userData enableIPv4:YES enableIPv6:YES];
- }
- - (id)initIPv4
- {
- return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:NO];
- }
- - (id)initIPv6
- {
- return [self initWithDelegate:nil userData:0 enableIPv4:NO enableIPv6:YES];
- }
- - (void) dealloc
- {
- [self close];
- [theSendQueue release];
- [theReceiveQueue release];
- [theRunLoopModes release];
- [cachedLocalHost release];
- [cachedConnectedHost release];
- [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onUdpSocketDidClose:) object:self];
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- [super dealloc];
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Accessors
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (id)delegate
- {
- return theDelegate;
- }
- - (void)setDelegate:(id)delegate
- {
- theDelegate = delegate;
- }
- - (long)userData
- {
- return theUserData;
- }
- - (void)setUserData:(long)userData
- {
- theUserData = userData;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Run Loop
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)runLoopAddSource:(CFRunLoopSourceRef)source
- {
- NSUInteger i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopAddSource(theRunLoop, source, runLoopMode);
- }
- }
- - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source
- {
- NSUInteger i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopRemoveSource(theRunLoop, source, runLoopMode);
- }
- }
- - (void)runLoopAddTimer:(NSTimer *)timer
- {
- NSUInteger i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
- }
- }
- - (void)runLoopRemoveTimer:(NSTimer *)timer
- {
- NSUInteger i, count = [theRunLoopModes count];
- for(i = 0; i < count; i++)
- {
- CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
- CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Configuration
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (UInt32)maxReceiveBufferSize
- {
- return maxReceiveBufferSize;
- }
- - (void)setMaxReceiveBufferSize:(UInt32)max
- {
- maxReceiveBufferSize = max;
- }
- /**
- * See the header file for a full explanation of this method.
- **/
- - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"moveToRunLoop must be called from within the current RunLoop!");
-
- if(runLoop == nil)
- {
- return NO;
- }
- if(theRunLoop == [runLoop getCFRunLoop])
- {
- return YES;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueSendScheduled;
- theFlags &= ~kDequeueReceiveScheduled;
-
- if(theSource4) [self runLoopRemoveSource:theSource4];
- if(theSource6) [self runLoopRemoveSource:theSource6];
-
- // We do not retain the timers - they get retained by the runloop when we add them as a source.
- // Since we're about to remove them as a source, we retain now, and release again below.
- [theSendTimer retain];
- [theReceiveTimer retain];
-
- if(theSendTimer) [self runLoopRemoveTimer:theSendTimer];
- if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer];
-
- theRunLoop = [runLoop getCFRunLoop];
-
- if(theSendTimer) [self runLoopAddTimer:theSendTimer];
- if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer];
-
- // Release timers since we retained them above
- [theSendTimer release];
- [theReceiveTimer release];
-
- if(theSource4) [self runLoopAddSource:theSource4];
- if(theSource6) [self runLoopAddSource:theSource6];
-
- [runLoop performSelector:@selector(maybeDequeueSend) target:self argument:nil order:0 modes:theRunLoopModes];
- [runLoop performSelector:@selector(maybeDequeueReceive) target:self argument:nil order:0 modes:theRunLoopModes];
- [runLoop performSelector:@selector(maybeScheduleClose) target:self argument:nil order:0 modes:theRunLoopModes];
-
- return YES;
- }
- /**
- * See the header file for a full explanation of this method.
- **/
- - (BOOL)setRunLoopModes:(NSArray *)runLoopModes
- {
- NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
- @"setRunLoopModes must be called from within the current RunLoop!");
-
- if([runLoopModes count] == 0)
- {
- return NO;
- }
- if([theRunLoopModes isEqualToArray:runLoopModes])
- {
- return YES;
- }
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
- theFlags &= ~kDequeueSendScheduled;
- theFlags &= ~kDequeueReceiveScheduled;
-
- if(theSource4) [self runLoopRemoveSource:theSource4];
- if(theSource6) [self runLoopRemoveSource:theSource6];
-
- // We do not retain the timers - they get retained by the runloop when we add them as a source.
- // Since we're about to remove them as a source, we retain now, and release again below.
- [theSendTimer retain];
- [theReceiveTimer retain];
-
- if(theSendTimer) [self runLoopRemoveTimer:theSendTimer];
- if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer];
-
- [theRunLoopModes release];
- theRunLoopModes = [runLoopModes copy];
-
- if(theSendTimer) [self runLoopAddTimer:theSendTimer];
- if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer];
-
- // Release timers since we retained them above
- [theSendTimer release];
- [theReceiveTimer release];
-
- if(theSource4) [self runLoopAddSource:theSource4];
- if(theSource6) [self runLoopAddSource:theSource6];
-
- [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- [self performSelector:@selector(maybeScheduleClose) withObject:nil afterDelay:0 inModes:theRunLoopModes];
-
- return YES;
- }
- - (NSArray *)runLoopModes
- {
- return [[theRunLoopModes retain] autorelease];
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Utilities:
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure.
- * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6.
- *
- * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo).
- **/
- - (int)convertForBindHost:(NSString *)host
- port:(UInt16)port
- intoAddress4:(NSData **)address4
- address6:(NSData **)address6
- {
- if(host == nil || ([host length] == 0))
- {
- // Use 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.
- if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
- if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
-
- return 0;
- }
- else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
- {
- // Note: getaddrinfo("localhost",...) fails on 10.5.3
-
- // Use LOOPBACK address
- struct sockaddr_in nativeAddr;
- nativeAddr.sin_len = sizeof(struct sockaddr_in);
- nativeAddr.sin_family = AF_INET;
- nativeAddr.sin_port = htons(port);
- nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
-
- struct sockaddr_in6 nativeAddr6;
- nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
- nativeAddr6.sin6_family = AF_INET6;
- nativeAddr6.sin6_port = htons(port);
- nativeAddr6.sin6_flowinfo = 0;
- nativeAddr6.sin6_addr = in6addr_loopback;
- nativeAddr6.sin6_scope_id = 0;
-
- // Wrap the native address structures for CFSocketSetAddress.
- if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
- if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
-
- return 0;
- }
- else
- {
- NSString *portStr = [NSString stringWithFormat:@"%hu", port];
-
- #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- @synchronized (getaddrinfoLock)
- #endif
- {
- struct addrinfo hints, *res, *res0;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_DGRAM;
- hints.ai_protocol = IPPROTO_UDP;
- hints.ai_flags = AI_PASSIVE;
-
- int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
-
- if(error) return error;
-
- for(res = res0; res; res = res->ai_next)
- {
- if(address4 && !*address4 && (res->ai_family == AF_INET))
- {
- // Found IPv4 address
- // Wrap the native address structures for CFSocketSetAddress.
- if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- else if(address6 && !*address6 && (res->ai_family == AF_INET6))
- {
- // Found IPv6 address
- // Wrap the native address structures for CFSocketSetAddress.
- if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- }
- freeaddrinfo(res0);
- }
-
- return 0;
- }
- }
- /**
- * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure.
- * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6.
- *
- * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo).
- **/
- - (int)convertForSendHost:(NSString *)host
- port:(UInt16)port
- intoAddress4:(NSData **)address4
- address6:(NSData **)address6
- {
- if(host == nil || ([host length] == 0))
- {
- // We're not binding, so what are we supposed to do with this?
- return EAI_NONAME;
- }
- else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
- {
- // Note: getaddrinfo("localhost",...) fails on 10.5.3
-
- // Use LOOPBACK address
- struct sockaddr_in nativeAddr;
- nativeAddr.sin_len = sizeof(struct sockaddr_in);
- nativeAddr.sin_family = AF_INET;
- nativeAddr.sin_port = htons(port);
- nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
-
- struct sockaddr_in6 nativeAddr6;
- nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
- nativeAddr6.sin6_family = AF_INET6;
- nativeAddr6.sin6_port = htons(port);
- nativeAddr6.sin6_flowinfo = 0;
- nativeAddr6.sin6_addr = in6addr_loopback;
- nativeAddr6.sin6_scope_id = 0;
-
- // Wrap the native address structures for CFSocketSetAddress.
- if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
- if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
-
- return 0;
- }
- else
- {
- NSString *portStr = [NSString stringWithFormat:@"%hu", port];
- #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
- @synchronized (getaddrinfoLock)
- #endif
- {
- struct addrinfo hints, *res, *res0;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = PF_UNSPEC;
- hints.ai_socktype = SOCK_DGRAM;
- hints.ai_protocol = IPPROTO_UDP;
- // No passive flag on a send or connect
-
- int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
-
- if(error) return error;
-
- for(res = res0; res; res = res->ai_next)
- {
- if(address4 && !*address4 && (res->ai_family == AF_INET))
- {
- // Found IPv4 address
- // Wrap the native address structures for CFSocketSetAddress.
- if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- else if(address6 && !*address6 && (res->ai_family == AF_INET6))
- {
- // Found IPv6 address
- // Wrap the native address structures for CFSocketSetAddress.
- if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
- }
- }
- freeaddrinfo(res0);
- }
-
- return 0;
- }
- }
- - (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4
- {
- char addrBuf[INET_ADDRSTRLEN];
-
- if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, sizeof(addrBuf)) == NULL)
- {
- [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."];
- }
-
- return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
- }
- - (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6
- {
- char addrBuf[INET6_ADDRSTRLEN];
-
- if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, sizeof(addrBuf)) == NULL)
- {
- [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."];
- }
-
- return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
- }
- - (NSString *)addressHost:(struct sockaddr *)pSockaddr
- {
- if(pSockaddr->sa_family == AF_INET)
- {
- return [self addressHost4:(struct sockaddr_in *)pSockaddr];
- }
- else
- {
- return [self addressHost6:(struct sockaddr_in6 *)pSockaddr];
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Socket Implementation:
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * Binds the underlying socket(s) to the given port.
- * The socket(s) will be able to receive data on any interface.
- *
- * On success, returns YES.
- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
- **/
- - (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr
- {
- return [self bindToAddress:nil port:port error:errPtr];
- }
- /**
- * Binds the underlying socket(s) to the given address and port.
- * The sockets(s) will be able to receive data only on the given interface.
- *
- * To receive data on any interface, pass nil or "".
- * To receive data only on the loopback interface, pass "localhost" or "loopback".
- *
- * On success, returns YES.
- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
- **/
- - (BOOL)bindToAddress:(NSString *)host port:(UInt16)port error:(NSError **)errPtr
- {
- if(theFlags & kDidClose)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"The socket is closed."];
- }
- if(theFlags & kDidBind)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Cannot bind a socket more than once."];
- }
- if(theFlags & kDidConnect)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Cannot bind after connecting. If needed, bind first, then connect."];
- }
-
- // Convert the given host/port into native address structures for CFSocketSetAddress
- NSData *address4 = nil, *address6 = nil;
-
- int gai_error = [self convertForBindHost:host port:port intoAddress4:&address4 address6:&address6];
- if(gai_error)
- {
- if(errPtr)
- {
- NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:info];
- }
- return NO;
- }
-
- NSAssert((address4 || address6), @"address4 and address6 are nil");
-
- // Set the SO_REUSEADDR flags
-
- int reuseOn = 1;
- if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
- if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
-
- // Bind the sockets
-
- if(address4)
- {
- if(theSocket4)
- {
- CFSocketError error = CFSocketSetAddress(theSocket4, (CFDataRef)address4);
- if(error != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
-
- if(!address6)
- {
- // Using IPv4 only
- [self closeSocket6];
- }
- }
- else if(!address6)
- {
- if(errPtr) *errPtr = [self getIPv4UnavailableError];
- return NO;
- }
- }
-
- if(address6)
- {
- // Note: The iPhone doesn't currently support IPv6
-
- if(theSocket6)
- {
- CFSocketError error = CFSocketSetAddress(theSocket6, (CFDataRef)address6);
- if(error != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
-
- if(!address4)
- {
- // Using IPv6 only
- [self closeSocket4];
- }
- }
- else if(!address4)
- {
- if(errPtr) *errPtr = [self getIPv6UnavailableError];
- return NO;
- }
- }
-
- theFlags |= kDidBind;
- return YES;
- }
- /**
- * Connects the underlying UDP socket to the given host and port.
- * If an IPv4 address is resolved, the IPv4 socket is connected, and the IPv6 socket is invalidated and released.
- * If an IPv6 address is resolved, the IPv6 socket is connected, and the IPv4 socket is invalidated and released.
- *
- * On success, returns YES.
- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
- **/
- - (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr
- {
- if(theFlags & kDidClose)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"The socket is closed."];
- }
- if(theFlags & kDidConnect)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Cannot connect a socket more than once."];
- }
-
- // Convert the given host/port into native address structures for CFSocketSetAddress
- NSData *address4 = nil, *address6 = nil;
-
- int error = [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6];
- 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];
- }
- return NO;
- }
-
- NSAssert((address4 || address6), @"address4 and address6 are nil");
-
- // We only want to connect via a single interface.
- // IPv4 is currently preferred, but this may change in the future.
-
- if(address4)
- {
- if(theSocket4)
- {
- CFSocketError sockErr = CFSocketConnectToAddress(theSocket4, (CFDataRef)address4, (CFTimeInterval)0.0);
- if(sockErr != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- theFlags |= kDidConnect;
-
- // We're connected to an IPv4 address, so no need for the IPv6 socket
- [self closeSocket6];
-
- return YES;
- }
- else if(!address6)
- {
- if(errPtr) *errPtr = [self getIPv4UnavailableError];
- return NO;
- }
- }
-
- if(address6)
- {
- // Note: The iPhone doesn't currently support IPv6
-
- if(theSocket6)
- {
- CFSocketError sockErr = CFSocketConnectToAddress(theSocket6, (CFDataRef)address6, (CFTimeInterval)0.0);
- if(sockErr != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- theFlags |= kDidConnect;
-
- // We're connected to an IPv6 address, so no need for the IPv4 socket
- [self closeSocket4];
-
- return YES;
- }
- else
- {
- if(errPtr) *errPtr = [self getIPv6UnavailableError];
- return NO;
- }
- }
-
- // It shouldn't be possible to get to this point because either address4 or address6 was non-nil.
- if(errPtr) *errPtr = nil;
- return NO;
- }
- /**
- * Connects the underlying UDP socket to the remote address.
- * If the address is an IPv4 address, the IPv4 socket is connected, and the IPv6 socket is invalidated and released.
- * If the address is an IPv6 address, the IPv6 socket is connected, and the IPv4 socket is invalidated and released.
- *
- * The address is a native address structure, as may be returned from API's such as Bonjour.
- * An address may be created manually by simply wrapping a sockaddr_in or sockaddr_in6 in an NSData object.
- *
- * On success, returns YES.
- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
- **/
- - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
- {
- if(theFlags & kDidClose)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"The socket is closed."];
- }
- if(theFlags & kDidConnect)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Cannot connect a socket more than once."];
- }
-
- // Is remoteAddr an IPv4 address?
- if([remoteAddr length] == sizeof(struct sockaddr_in))
- {
- if(theSocket4)
- {
- CFSocketError error = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, (CFTimeInterval)0.0);
- if(error != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- theFlags |= kDidConnect;
-
- // We're connected to an IPv4 address, so no need for the IPv6 socket
- [self closeSocket6];
-
- return YES;
- }
- else
- {
- if(errPtr) *errPtr = [self getIPv4UnavailableError];
- return NO;
- }
- }
-
- // Is remoteAddr an IPv6 address?
- if([remoteAddr length] == sizeof(struct sockaddr_in6))
- {
- if(theSocket6)
- {
- CFSocketError error = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, (CFTimeInterval)0.0);
- if(error != kCFSocketSuccess)
- {
- if(errPtr) *errPtr = [self getSocketError];
- return NO;
- }
- theFlags |= kDidConnect;
-
- // We're connected to an IPv6 address, so no need for the IPv4 socket
- [self closeSocket4];
-
- return YES;
- }
- else
- {
- if(errPtr) *errPtr = [self getIPv6UnavailableError];
- return NO;
- }
- }
-
- // The remoteAddr was invalid
- if(errPtr)
- {
- NSString *errMsg = @"remoteAddr parameter is not a valid address";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain
- code:AsyncUdpSocketBadParameter
- userInfo:info];
- }
- return NO;
- }
- /**
- * Join multicast group
- *
- * Group should be a multicast IP address (eg. @"239.255.250.250" for IPv4).
- * Address is local interface for IPv4, but currently defaults under IPv6.
- **/
- - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
- {
- return [self joinMulticastGroup:group withAddress:nil error:errPtr];
- }
- - (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)address error:(NSError **)errPtr
- {
- if(theFlags & kDidClose)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"The socket is closed."];
- }
- if(!(theFlags & kDidBind))
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Must bind a socket before joining a multicast group."];
- }
- if(theFlags & kDidConnect)
- {
- [NSException raise:AsyncUdpSocketException
- format:@"Cannot join a multicast group if connected."];
- }
-
- // Get local interface address
- // Convert the given host/port into native address structures for CFSocketSetAddress
- NSData *address4 = nil, *address6 = nil;
-
- int error = [self convertForBindHost:address port:0 intoAddress4:&address4 address6:&address6];
- if(error)
- {
- if(errPtr)
- {
- NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
- NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'address': %@", errMsg];
- NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
- }
- return NO;
- }
-
- NSAssert((address4 || address6), @"address4 and address6 are nil");
-
- // Get multicast address (group)
- NSData *group4 = nil, *group6 = nil;
- error = [self convertForBindHost:group port:0 intoAddress4:&group4 address6:&group6];
- if(error)
- {
- if(errPtr)
- {
- NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
- NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'group': %@", errMsg];
- NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
- }
- return NO;
- }
-
- NSAssert((group4 || group6), @"group4 and group6 are nil");
-
- if(theSocket4 && group4 && address4)
- {
- const struct sockaddr_in* nativeAddress = [address4 bytes];
- const struct sockaddr_in* nativeGroup = [group4 bytes];
-
- struct ip_mreq imreq;
- imreq.imr_multiaddr = nativeGroup->sin_addr;
- imreq.imr_interface = nativeAddress->sin_addr;
-
- // JOIN multicast group on default interface
- error = setsockopt(CFSocketGetNative(theSocket4), IPPROTO_IP, IP_ADD_MEMBERSHIP,
- (const void *)&imreq, sizeof(struct ip_mreq));
- if(error)
- {
- if(errPtr)
- {
- NSString *errMsg = @"Unable to join IPv4 multicast group";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
- }
- return NO;
- }
-
- // Using IPv4 only
- [self closeSocket6];
-
- return YES;
- }
-
- if(theSocket6 && group6 && address6)
- {
- const struct sockaddr_in6* nativeGroup = [group6 bytes];
-
- struct ipv6_mreq imreq;
- imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
- imreq.ipv6mr_interface = 0;
-
- // JOIN multicast group on default interface
- error = setsockopt(CFSocketGetNative(theSocket6), IPPROTO_IP, IPV6_JOIN_GROUP,
- (const void *)&imreq, sizeof(struct ipv6_mreq));
- if(error)
- {
- if(errPtr)
- {
- NSString *errMsg = @"Unable to join IPv6 multicast group";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
- }
- return NO;
- }
-
- // Using IPv6 only
- [self closeSocket4];
-
- return YES;
- }
-
- // The given address and group didn't match the existing socket(s).
- // This means there were no compatible combination of all IPv4 or IPv6 socket, group and address.
- if(errPtr)
- {
- NSString *errMsg = @"Invalid group and/or address, not matching existing socket(s)";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain
- code:AsyncUdpSocketBadParameter
- userInfo:info];
- }
- return NO;
- }
- /**
- * By default, the underlying socket in the OS will not allow you to send broadcast messages.
- * In order to send broadcast messages, you need to enable this functionality in the socket.
- *
- * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
- * delivered to every host on the network.
- * The reason this is generally disabled by default is to prevent
- * accidental broadcast messages from flooding the network.
- **/
- - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
- {
- if (theSocket4)
- {
- int value = flag ? 1 : 0;
- int error = setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_BROADCAST,
- (const void *)&value, sizeof(value));
- if(error)
- {
- if(errPtr)
- {
- NSString *errMsg = @"Unable to enable broadcast message sending";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
- }
- return NO;
- }
- }
-
- // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
- // The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
-
- return YES;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Disconnect Implementation:
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (void)emptyQueues
- {
- if (theCurrentSend) [self endCurrentSend];
- if (theCurrentReceive) [self endCurrentReceive];
-
- [theSendQueue removeAllObjects];
- [theReceiveQueue removeAllObjects];
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueSend) object:nil];
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueReceive) object:nil];
-
- theFlags &= ~kDequeueSendScheduled;
- theFlags &= ~kDequeueReceiveScheduled;
- }
- - (void)closeSocket4
- {
- if (theSocket4 != NULL)
- {
- CFSocketInvalidate(theSocket4);
- CFRelease(theSocket4);
- theSocket4 = NULL;
- }
- if (theSource4 != NULL)
- {
- [self runLoopRemoveSource:theSource4];
- CFRelease(theSource4);
- theSource4 = NULL;
- }
- }
- - (void)closeSocket6
- {
- if (theSocket6 != NULL)
- {
- CFSocketInvalidate(theSocket6);
- CFRelease(theSocket6);
- theSocket6 = NULL;
- }
- if (theSource6 != NULL)
- {
- [self runLoopRemoveSource:theSource6];
- CFRelease(theSource6);
- theSource6 = NULL;
- }
- }
- - (void)close
- {
- [self emptyQueues];
- [self closeSocket4];
- [self closeSocket6];
-
- theRunLoop = NULL;
-
- // Delay notification to give user freedom to release without returning here and core-dumping.
- if ([theDelegate respondsToSelector:@selector(onUdpSocketDidClose:)])
- {
- [theDelegate performSelector:@selector(onUdpSocketDidClose:)
- withObject:self
- afterDelay:0
- inModes:theRunLoopModes];
- }
-
- theFlags |= kDidClose;
- }
- - (void)closeAfterSending
- {
- if(theFlags & kDidClose) return;
-
- theFlags |= (kForbidSendReceive | kCloseAfterSends);
- [self maybeScheduleClose];
- }
- - (void)closeAfterReceiving
- {
- if(theFlags & kDidClose) return;
-
- theFlags |= (kForbidSendReceive | kCloseAfterReceives);
- [self maybeScheduleClose];
- }
- - (void)closeAfterSendingAndReceiving
- {
- if(theFlags & kDidClose) return;
-
- theFlags |= (kForbidSendReceive | kCloseAfterSends | kCloseAfterReceives);
- [self maybeScheduleClose];
- }
- - (void)maybeScheduleClose
- {
- BOOL shouldDisconnect = NO;
-
- if(theFlags & kCloseAfterSends)
- {
- if(([theSendQueue count] == 0) && (theCurrentSend == nil))
- {
- if(theFlags & kCloseAfterReceives)
- {
- if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil))
- {
- shouldDisconnect = YES;
- }
- }
- else
- {
- shouldDisconnect = YES;
- }
- }
- }
- else if(theFlags & kCloseAfterReceives)
- {
- if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil))
- {
- shouldDisconnect = YES;
- }
- }
-
- if(shouldDisconnect)
- {
- [self performSelector:@selector(close) withObject:nil afterDelay:0 inModes:theRunLoopModes];
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #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 = @"General CFSocket error";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketCFSocketError userInfo:info];
- }
- - (NSError *)getIPv4UnavailableError
- {
- NSString *errMsg = @"IPv4 is unavailable due to binding/connecting using IPv6 only";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv4Unavailable userInfo:info];
- }
- - (NSError *)getIPv6UnavailableError
- {
- NSString *errMsg = @"IPv6 is unavailable due to binding/connecting using IPv4 only or is not supported on this platform";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv6Unavailable userInfo:info];
- }
- - (NSError *)getSendTimeoutError
- {
- NSString *errMsg = @"Send operation timed out";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketSendTimeoutError userInfo:info];
- }
- - (NSError *)getReceiveTimeoutError
- {
- NSString *errMsg = @"Receive operation timed out";
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
-
- return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketReceiveTimeoutError userInfo:info];
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Diagnostics
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (NSString *)localHost
- {
- if(cachedLocalHost) return cachedLocalHost;
-
- if(theSocket4)
- return [self localHost:theSocket4];
- else
- return [self localHost:theSocket6];
- }
- - (UInt16)localPort
- {
- if(cachedLocalPort > 0) return cachedLocalPort;
-
- if(theSocket4)
- return [self localPort:theSocket4];
- else
- return [self localPort:theSocket6];
- }
- - (NSString *)connectedHost
- {
- if(cachedConnectedHost) return cachedConnectedHost;
-
- if(theSocket4)
- return [self connectedHost:theSocket4];
- else
- return [self connectedHost:theSocket6];
- }
- - (UInt16)connectedPort
- {
- if(cachedConnectedPort > 0) return cachedConnectedPort;
-
- if(theSocket4)
- return [self connectedPort:theSocket4];
- else
- return [self connectedPort:theSocket6];
- }
- - (NSString *)localHost:(CFSocketRef)theSocket
- {
- if(theSocket == NULL) return nil;
-
- // Unfortunately we can't use CFSocketCopyAddress.
- // The CFSocket library caches the address the first time you call CFSocketCopyAddress.
- // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary,
- // and will continue to return the old value of the socket address.
-
- NSString *result = nil;
-
- if(theSocket == theSocket4)
- {
- struct sockaddr_in sockaddr4;
- socklen_t sockaddr4len = sizeof(sockaddr4);
-
- if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
- {
- return nil;
- }
- result = [self addressHost4:&sockaddr4];
- }
- else
- {
- struct sockaddr_in6 sockaddr6;
- socklen_t sockaddr6len = sizeof(sockaddr6);
-
- if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
- {
- return nil;
- }
- result = [self addressHost6:&sockaddr6];
- }
-
- if(theFlags & kDidBind)
- {
- [cachedLocalHost release];
- cachedLocalHost = [result copy];
- }
-
- return result;
- }
- - (UInt16)localPort:(CFSocketRef)theSocket
- {
- if(theSocket == NULL) return 0;
-
- // Unfortunately we can't use CFSocketCopyAddress.
- // The CFSocket library caches the address the first time you call CFSocketCopyAddress.
- // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary,
- // and will continue to return the old value of the socket address.
-
- UInt16 result = 0;
-
- if(theSocket == theSocket4)
- {
- struct sockaddr_in sockaddr4;
- socklen_t sockaddr4len = sizeof(sockaddr4);
-
- if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
- {
- return 0;
- }
- result = ntohs(sockaddr4.sin_port);
- }
- else
- {
- struct sockaddr_in6 sockaddr6;
- socklen_t sockaddr6len = sizeof(sockaddr6);
-
- if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
- {
- return 0;
- }
- result = ntohs(sockaddr6.sin6_port);
- }
-
- if(theFlags & kDidBind)
- {
- cachedLocalPort = result;
- }
-
- return result;
- }
- - (NSString *)connectedHost:(CFSocketRef)theSocket
- {
- if(theSocket == NULL) return nil;
-
- // Unfortunately we can't use CFSocketCopyPeerAddress.
- // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress.
- // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary,
- // and will continue to return the old value of the socket peer address.
-
- NSString *result = nil;
-
- if(theSocket == theSocket4)
- {
- struct sockaddr_in sockaddr4;
- socklen_t sockaddr4len = sizeof(sockaddr4);
-
- if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
- {
- return nil;
- }
- result = [self addressHost4:&sockaddr4];
- }
- else
- {
- struct sockaddr_in6 sockaddr6;
- socklen_t sockaddr6len = sizeof(sockaddr6);
-
- if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
- {
- return nil;
- }
- result = [self addressHost6:&sockaddr6];
- }
-
- if(theFlags & kDidConnect)
- {
- [cachedConnectedHost release];
- cachedConnectedHost = [result copy];
- }
-
- return result;
- }
- - (UInt16)connectedPort:(CFSocketRef)theSocket
- {
- if(theSocket == NULL) return 0;
-
- // Unfortunately we can't use CFSocketCopyPeerAddress.
- // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress.
- // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary,
- // and will continue to return the old value of the socket peer address.
-
- UInt16 result = 0;
-
- if(theSocket == theSocket4)
- {
- struct sockaddr_in sockaddr4;
- socklen_t sockaddr4len = sizeof(sockaddr4);
-
- if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
- {
- return 0;
- }
- result = ntohs(sockaddr4.sin_port);
- }
- else
- {
- struct sockaddr_in6 sockaddr6;
- socklen_t sockaddr6len = sizeof(sockaddr6);
-
- if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
- {
- return 0;
- }
- result = ntohs(sockaddr6.sin6_port);
- }
-
- if(theFlags & kDidConnect)
- {
- cachedConnectedPort = result;
- }
-
- return result;
- }
- - (BOOL)isConnected
- {
- return (((theFlags & kDidConnect) != 0) && ((theFlags & kDidClose) == 0));
- }
- - (BOOL)isConnectedToHost:(NSString *)host port:(UInt16)port
- {
- return [[self connectedHost] isEqualToString:host] && ([self connectedPort] == port);
- }
- - (BOOL)isClosed
- {
- return (theFlags & kDidClose) ? YES : NO;
- }
- - (BOOL)isIPv4
- {
- return (theSocket4 != NULL);
- }
- - (BOOL)isIPv6
- {
- return (theSocket6 != NULL);
- }
- - (unsigned int)maximumTransmissionUnit
- {
- CFSocketNativeHandle theNativeSocket;
- if(theSocket4)
- theNativeSocket = CFSocketGetNative(theSocket4);
- else if(theSocket6)
- theNativeSocket = CFSocketGetNative(theSocket6);
- else
- return 0;
-
- if(theNativeSocket == 0)
- {
- return 0;
- }
-
- struct ifreq ifr;
- bzero(&ifr, sizeof(ifr));
-
- if(if_indextoname(theNativeSocket, ifr.ifr_name) == NULL)
- {
- return 0;
- }
-
- if(ioctl(theNativeSocket, SIOCGIFMTU, &ifr) >= 0)
- {
- return ifr.ifr_mtu;
- }
-
- return 0;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Sending
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
- {
- if((data == nil) || ([data length] == 0)) return NO;
- if(theFlags & kForbidSendReceive) return NO;
- if(theFlags & kDidClose) return NO;
-
- // This method is only for connected sockets
- if(![self isConnected]) return NO;
-
- AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:nil timeout:timeout tag:tag];
-
- [theSendQueue addObject:packet];
- [self scheduleDequeueSend];
-
- [packet release];
- return YES;
- }
- - (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag
- {
- if((data == nil) || ([data length] == 0)) return NO;
- if(theFlags & kForbidSendReceive) return NO;
- if(theFlags & kDidClose) return NO;
-
- // This method is only for non-connected sockets
- if([self isConnected]) return NO;
-
- NSData *address4 = nil, *address6 …
Large files files are truncated, but you can click here to view the full file