// // 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 #import #import #import #import #import #if TARGET_OS_IPHONE // Note: You may need to add the CFNetwork Framework to your project #import #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 { unsigned i, count = [theRunLoopModes count]; for(i = 0; i < count; i++) { CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; CFRunLoopAddSource(theRunLoop, source, runLoopMode); } } - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source { unsigned i, count = [theRunLoopModes count]; for(i = 0; i < count; i++) { CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; CFRunLoopRemoveSource(theRunLoop, source, runLoopMode); } } - (void)runLoopAddTimer:(NSTimer *)timer { unsigned i, count = [theRunLoopModes count]; for(i = 0; i < count; i++) { CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); } } - (void)runLoopRemoveTimer:(NSTimer *)timer { unsigned i, count = [theRunLoopModes count]; for(i = 0; i < count; i++) { CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #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 = nil; [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6]; AsyncSendPacket *packet = nil; if(address4 && theSocket4) packet = [[AsyncSendPacket alloc] initWithData:data address:address4 timeout:timeout tag:tag]; else if(address6 && theSocket6) packet = [[AsyncSendPacket alloc] initWithData:data address:address6 timeout:timeout tag:tag]; else return NO; [theSendQueue addObject:packet]; [self scheduleDequeueSend]; [packet release]; return YES; } - (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr 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; if([remoteAddr length] == sizeof(struct sockaddr_in) && !theSocket4) return NO; if([remoteAddr length] == sizeof(struct sockaddr_in6) && !theSocket6) return NO; AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:remoteAddr timeout:timeout tag:tag]; [theSendQueue addObject:packet]; [self scheduleDequeueSend]; [packet release]; return YES; } - (BOOL)canAcceptBytes:(CFSocketRef)sockRef { if(sockRef == theSocket4) { if(theFlags & kSock4CanAcceptBytes) return YES; } else { if(theFlags & kSock6CanAcceptBytes) return YES; } CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); if(theNativeSocket == 0) { NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); return NO; } fd_set fds; FD_ZERO(&fds); FD_SET(theNativeSocket, &fds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; return select(FD_SETSIZE, NULL, &fds, NULL, &timeout) > 0; } - (CFSocketRef)socketForPacket:(AsyncSendPacket *)packet { if(!theSocket4) return theSocket6; if(!theSocket6) return theSocket4; return ([packet->address length] == sizeof(struct sockaddr_in)) ? theSocket4 : theSocket6; } /** * Puts a maybeDequeueSend on the run loop. **/ - (void)scheduleDequeueSend { if((theFlags & kDequeueSendScheduled) == 0) { theFlags |= kDequeueSendScheduled; [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes]; } } /** * This method starts a new send, if needed. * It is called when a user requests a send. **/ - (void)maybeDequeueSend { // Unset the flag indicating a call to this method is scheduled theFlags &= ~kDequeueSendScheduled; if(theCurrentSend == nil) { if([theSendQueue count] > 0) { // Dequeue next send packet theCurrentSend = [[theSendQueue objectAtIndex:0] retain]; [theSendQueue removeObjectAtIndex:0]; // Start time-out timer. if(theCurrentSend->timeout >= 0.0) { theSendTimer = [NSTimer timerWithTimeInterval:theCurrentSend->timeout target:self selector:@selector(doSendTimeout:) userInfo:nil repeats:NO]; [self runLoopAddTimer:theSendTimer]; } // Immediately send, if possible. [self doSend:[self socketForPacket:theCurrentSend]]; } else if(theFlags & kCloseAfterSends) { if(theFlags & kCloseAfterReceives) { if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) { [self close]; } } else { [self close]; } } } } /** * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. **/ - (void)doSend:(CFSocketRef)theSocket { if(theCurrentSend != nil) { if(theSocket != [self socketForPacket:theCurrentSend]) { // Current send is for the other socket return; } if([self canAcceptBytes:theSocket]) { int result; CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); const void *buf = [theCurrentSend->buffer bytes]; unsigned bufSize = [theCurrentSend->buffer length]; if([self isConnected]) { result = send(theNativeSocket, buf, bufSize, 0); } else { const void *dst = [theCurrentSend->address bytes]; unsigned dstSize = [theCurrentSend->address length]; result = sendto(theNativeSocket, buf, bufSize, 0, dst, dstSize); } if(theSocket == theSocket4) theFlags &= ~kSock4CanAcceptBytes; else theFlags &= ~kSock6CanAcceptBytes; if(result < 0) { [self failCurrentSend:[self getErrnoError]]; } else { // If it wasn't bound before, it's bound now theFlags |= kDidBind; [self completeCurrentSend]; } [self scheduleDequeueSend]; } else { // Request notification when the socket is ready to send more data CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); } } } - (void)completeCurrentSend { NSAssert (theCurrentSend, @"Trying to complete current send when there is no current send."); if ([theDelegate respondsToSelector:@selector(onUdpSocket:didSendDataWithTag:)]) { [theDelegate onUdpSocket:self didSendDataWithTag:theCurrentSend->tag]; } if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. } - (void)failCurrentSend:(NSError *)error { NSAssert (theCurrentSend, @"Trying to fail current send when there is no current send."); if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotSendDataWithTag:dueToError:)]) { [theDelegate onUdpSocket:self didNotSendDataWithTag:theCurrentSend->tag dueToError:error]; } if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. } /** * Ends the current send, and all associated variables such as the send timer. **/ - (void)endCurrentSend { NSAssert (theCurrentSend, @"Trying to end current send when there is no current send."); [theSendTimer invalidate]; theSendTimer = nil; [theCurrentSend release]; theCurrentSend = nil; } - (void)doSendTimeout:(NSTimer *)timer { if (timer != theSendTimer) return; // Old timer. Ignore it. if (theCurrentSend != nil) { [self failCurrentSend:[self getSendTimeoutError]]; [self scheduleDequeueSend]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Receiving //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag { if(theFlags & kForbidSendReceive) return; if(theFlags & kDidClose) return; AsyncReceivePacket *packet = [[AsyncReceivePacket alloc] initWithTimeout:timeout tag:tag]; [theReceiveQueue addObject:packet]; [self scheduleDequeueReceive]; [packet release]; } - (BOOL)hasBytesAvailable:(CFSocketRef)sockRef { if(sockRef == theSocket4) { if(theFlags & kSock4HasBytesAvailable) return YES; } else { if(theFlags & kSock6HasBytesAvailable) return YES; } CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); if(theNativeSocket == 0) { NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); return NO; } fd_set fds; FD_ZERO(&fds); FD_SET(theNativeSocket, &fds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; return select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0; } /** * Puts a maybeDequeueReceive on the run loop. **/ - (void)scheduleDequeueReceive { if((theFlags & kDequeueReceiveScheduled) == 0) { theFlags |= kDequeueReceiveScheduled; [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes]; } } /** * Starts a new receive operation if needed **/ - (void)maybeDequeueReceive { // Unset the flag indicating a call to this method is scheduled theFlags &= ~kDequeueReceiveScheduled; if (theCurrentReceive == nil) { if([theReceiveQueue count] > 0) { // Dequeue next receive packet theCurrentReceive = [[theReceiveQueue objectAtIndex:0] retain]; [theReceiveQueue removeObjectAtIndex:0]; // Start time-out timer. if (theCurrentReceive->timeout >= 0.0) { theReceiveTimer = [NSTimer timerWithTimeInterval:theCurrentReceive->timeout target:self selector:@selector(doReceiveTimeout:) userInfo:nil repeats:NO]; [self runLoopAddTimer:theReceiveTimer]; } // Immediately receive, if possible // We always check both sockets so we don't ever starve one of them. // We also check them in alternating orders to prevent starvation if both of them // have a continuous flow of incoming data. if(theFlags & kFlipFlop) { [self doReceive4]; [self doReceive6]; } else { [self doReceive6]; [self doReceive4]; } theFlags ^= kFlipFlop; } else if(theFlags & kCloseAfterReceives) { if(theFlags & kCloseAfterSends) { if(([theSendQueue count] == 0) && (theCurrentSend == nil)) { [self close]; } } else { [self close]; } } } } - (void)doReceive4 { if(theSocket4) [self doReceive:theSocket4]; } - (void)doReceive6 { if(theSocket6) [self doReceive:theSocket6]; } - (void)doReceive:(CFSocketRef)theSocket { if (theCurrentReceive != nil) { BOOL appIgnoredReceivedData; BOOL userIgnoredReceivedData; do { // Set or reset ignored variables. // If the app or user ignores the received data, we'll continue this do-while loop. appIgnoredReceivedData = NO; userIgnoredReceivedData = NO; if([self hasBytesAvailable:theSocket]) { int result; CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); // Allocate buffer for recvfrom operation. // If the operation is successful, we'll realloc the buffer to the appropriate size, // and create an NSData wrapper around it without needing to copy any bytes around. void *buf = malloc(maxReceiveBufferSize); size_t bufSize = maxReceiveBufferSize; if(theSocket == theSocket4) { struct sockaddr_in sockaddr4; socklen_t sockaddr4len = sizeof(sockaddr4); result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); if(result >= 0) { NSString *host = [self addressHost4:&sockaddr4]; UInt16 port = ntohs(sockaddr4.sin_port); if([self isConnected] && ![self isConnectedToHost:host port:port]) { // The user connected to an address, and the received data doesn't match the address. // This may happen if the data is received by the kernel prior to the connect call. appIgnoredReceivedData = YES; } else { if(result != bufSize) { buf = realloc(buf, result); } theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf length:result freeWhenDone:YES]; theCurrentReceive->host = [host retain]; theCurrentReceive->port = port; } } theFlags &= ~kSock4HasBytesAvailable; } else { struct sockaddr_in6 sockaddr6; socklen_t sockaddr6len = sizeof(sockaddr6); result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); if(result >= 0) { NSString *host = [self addressHost6:&sockaddr6]; UInt16 port = ntohs(sockaddr6.sin6_port); if([self isConnected] && ![self isConnectedToHost:host port:port]) { // The user connected to an address, and the received data doesn't match the address. // This may happen if the data is received by the kernel prior to the connect call. appIgnoredReceivedData = YES; } else { if(result != bufSize) { buf = realloc(buf, result); } theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf length:result freeWhenDone:YES]; theCurrentReceive->host = [host retain]; theCurrentReceive->port = port; } } theFlags &= ~kSock6HasBytesAvailable; } // Check to see if we need to free our alloc'd buffer // If the buffer is non-nil, this means it has taken ownership of the buffer if(theCurrentReceive->buffer == nil) { free(buf); } if(result < 0) { [self failCurrentReceive:[self getErrnoError]]; [self scheduleDequeueReceive]; } else if(!appIgnoredReceivedData) { BOOL finished = [self maybeCompleteCurrentReceive]; if(finished) { [self scheduleDequeueReceive]; } else { [theCurrentReceive->buffer release]; [theCurrentReceive->host release]; theCurrentReceive->buffer = nil; theCurrentReceive->host = nil; userIgnoredReceivedData = YES; } } } else { // Request notification when the socket is ready to receive more data CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); } } while(appIgnoredReceivedData || userIgnoredReceivedData); } } - (BOOL)maybeCompleteCurrentReceive { NSAssert (theCurrentReceive, @"Trying to complete current receive when there is no current receive."); BOOL finished = YES; if ([theDelegate respondsToSelector:@selector(onUdpSocket:didReceiveData:withTag:fromHost:port:)]) { finished = [theDelegate onUdpSocket:self didReceiveData:theCurrentReceive->buffer withTag:theCurrentReceive->tag fromHost:theCurrentReceive->host port:theCurrentReceive->port]; } if (finished) { if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. } return finished; } - (void)failCurrentReceive:(NSError *)error { NSAssert (theCurrentReceive, @"Trying to fail current receive when there is no current receive."); if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotReceiveDataWithTag:dueToError:)]) { [theDelegate onUdpSocket:self didNotReceiveDataWithTag:theCurrentReceive->tag dueToError:error]; } if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. } - (void)endCurrentReceive { NSAssert (theCurrentReceive, @"Trying to end current receive when there is no current receive."); [theReceiveTimer invalidate]; theReceiveTimer = nil; [theCurrentReceive release]; theCurrentReceive = nil; } - (void)doReceiveTimeout:(NSTimer *)timer { if (timer != theReceiveTimer) return; // Old timer. Ignore it. if (theCurrentReceive != nil) { [self failCurrentReceive:[self getReceiveTimeoutError]]; [self scheduleDequeueReceive]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark CF Callbacks //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)doCFSocketCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData { NSParameterAssert((sock == theSocket4) || (sock == theSocket6)); switch (type) { case kCFSocketReadCallBack: if(sock == theSocket4) theFlags |= kSock4HasBytesAvailable; else theFlags |= kSock6HasBytesAvailable; [self doReceive:sock]; break; case kCFSocketWriteCallBack: if(sock == theSocket4) theFlags |= kSock4CanAcceptBytes; else theFlags |= kSock6CanAcceptBytes; [self doSend:sock]; break; default: NSLog (@"AsyncUdpSocket %p received unexpected CFSocketCallBackType %d.", self, type); break; } } /** * 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]; AsyncUdpSocket *theSocket = [[(AsyncUdpSocket *)pInfo retain] autorelease]; [theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; [pool release]; } @end