From 867ab1a6c7d4291641ea40c24007b6e26923af9d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 18 Dec 2025 18:06:07 +0100 Subject: [PATCH] chore: Apply format to socket helpers --- .../Vendor/CocoaAsyncSocket/GCDAsyncSocket.m | 12732 ++++++++-------- .../CocoaAsyncSocket/GCDAsyncUdpSocket.m | 7990 +++++----- 2 files changed, 10610 insertions(+), 10112 deletions(-) diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m index 480c116a6..56508e809 100755 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +++ b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m @@ -111,14 +111,14 @@ * Seeing a return statements within an inner block * can sometimes be mistaken for a return point of the enclosing method. * This makes inline blocks a bit easier to read. -**/ + **/ #define return_from_block return /** * A socket file descriptor is really just an integer. * It represents the index of the socket within the kernel. * This makes invalid file descriptor comparisons easier to read. -**/ + **/ #define SOCKET_NULL -1 @@ -145,44 +145,44 @@ enum GCDAsyncSocketFlags { - kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) - kConnected = 1 << 1, // If set, the socket is connected - kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed - kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout - kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout - kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued - kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued - kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. - kReadSourceSuspended = 1 << 8, // If set, the read source is suspended - kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended - kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS - kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete - kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete - kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS - kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket - kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained - kDealloc = 1 << 16, // If set, the socket is being deallocated + kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting) + kConnected = 1 << 1, // If set, the socket is connected + kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed + kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout + kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout + kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued + kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued + kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown. + kReadSourceSuspended = 1 << 8, // If set, the read source is suspended + kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended + kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS + kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete + kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete + kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS + kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket + kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained + kDealloc = 1 << 16, // If set, the socket is being deallocated #if TARGET_OS_IPHONE - kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread - kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport - kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available + kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread + kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport + kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available #endif }; enum GCDAsyncSocketConfig { - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 - kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes + kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled + kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled + kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4 + kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes }; #if TARGET_OS_IPHONE - static NSThread *cfstreamThread; // Used for CFStreams +static NSThread *cfstreamThread; // Used for CFStreams - static uint64_t cfstreamThreadRetainCount; // setup & teardown - static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown +static uint64_t cfstreamThreadRetainCount; // setup & teardown +static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -205,15 +205,15 @@ * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed. * * The current design is very simple and straight-forward, while also keeping memory requirements lower. -**/ + **/ @interface GCDAsyncSocketPreBuffer : NSObject { - uint8_t *preBuffer; - size_t preBufferSize; + uint8_t *preBuffer; + size_t preBufferSize; - uint8_t *readPointer; - uint8_t *writePointer; + uint8_t *readPointer; + uint8_t *writePointer; } - (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER; @@ -242,104 +242,104 @@ @implementation GCDAsyncSocketPreBuffer // Cover the superclass' designated initializer - (instancetype)init NS_UNAVAILABLE { - NSAssert(0, @"Use the designated initializer"); - return nil; + NSAssert(0, @"Use the designated initializer"); + return nil; } - (instancetype)initWithCapacity:(size_t)numBytes { - if ((self = [super init])) - { - preBufferSize = numBytes; - preBuffer = malloc(preBufferSize); + if ((self = [super init])) + { + preBufferSize = numBytes; + preBuffer = malloc(preBufferSize); - readPointer = preBuffer; - writePointer = preBuffer; - } - return self; + readPointer = preBuffer; + writePointer = preBuffer; + } + return self; } - (void)dealloc { - if (preBuffer) - free(preBuffer); + if (preBuffer) + free(preBuffer); } - (void)ensureCapacityForWrite:(size_t)numBytes { - size_t availableSpace = [self availableSpace]; + size_t availableSpace = [self availableSpace]; - if (numBytes > availableSpace) - { - size_t additionalBytes = numBytes - availableSpace; + if (numBytes > availableSpace) + { + size_t additionalBytes = numBytes - availableSpace; - size_t newPreBufferSize = preBufferSize + additionalBytes; - uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); + size_t newPreBufferSize = preBufferSize + additionalBytes; + uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize); - size_t readPointerOffset = readPointer - preBuffer; - size_t writePointerOffset = writePointer - preBuffer; + size_t readPointerOffset = readPointer - preBuffer; + size_t writePointerOffset = writePointer - preBuffer; - preBuffer = newPreBuffer; - preBufferSize = newPreBufferSize; + preBuffer = newPreBuffer; + preBufferSize = newPreBufferSize; - readPointer = preBuffer + readPointerOffset; - writePointer = preBuffer + writePointerOffset; - } + readPointer = preBuffer + readPointerOffset; + writePointer = preBuffer + writePointerOffset; + } } - (size_t)availableBytes { - return writePointer - readPointer; + return writePointer - readPointer; } - (uint8_t *)readBuffer { - return readPointer; + return readPointer; } - (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr { - if (bufferPtr) *bufferPtr = readPointer; - if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; + if (bufferPtr) *bufferPtr = readPointer; + if (availableBytesPtr) *availableBytesPtr = [self availableBytes]; } - (void)didRead:(size_t)bytesRead { - readPointer += bytesRead; + readPointer += bytesRead; - if (readPointer == writePointer) - { - // The prebuffer has been drained. Reset pointers. - readPointer = preBuffer; - writePointer = preBuffer; - } + if (readPointer == writePointer) + { + // The prebuffer has been drained. Reset pointers. + readPointer = preBuffer; + writePointer = preBuffer; + } } - (size_t)availableSpace { - return preBufferSize - (writePointer - preBuffer); + return preBufferSize - (writePointer - preBuffer); } - (uint8_t *)writeBuffer { - return writePointer; + return writePointer; } - (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr { - if (bufferPtr) *bufferPtr = writePointer; - if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; + if (bufferPtr) *bufferPtr = writePointer; + if (availableSpacePtr) *availableSpacePtr = [self availableSpace]; } - (void)didWrite:(size_t)bytesWritten { - writePointer += bytesWritten; + writePointer += bytesWritten; } - (void)reset { - readPointer = preBuffer; - writePointer = preBuffer; + readPointer = preBuffer; + writePointer = preBuffer; } @end @@ -354,20 +354,20 @@ - (void)reset * - reading to a certain length * - reading to a certain separator * - or simply reading the first chunk of available data -**/ + **/ @interface GCDAsyncReadPacket : NSObject { - @public - NSMutableData *buffer; - NSUInteger startOffset; - NSUInteger bytesDone; - NSUInteger maxLength; - NSTimeInterval timeout; - NSUInteger readLength; - NSData *term; - BOOL bufferOwner; - NSUInteger originalBufferLength; - long tag; +@public + NSMutableData *buffer; + NSUInteger startOffset; + NSUInteger bytesDone; + NSUInteger maxLength; + NSTimeInterval timeout; + NSUInteger readLength; + NSData *term; + BOOL bufferOwner; + NSUInteger originalBufferLength; + long tag; } - (instancetype)initWithData:(NSMutableData *)d startOffset:(NSUInteger)s @@ -394,8 +394,8 @@ @implementation GCDAsyncReadPacket // Cover the superclass' designated initializer - (instancetype)init NS_UNAVAILABLE { - NSAssert(0, @"Use the designated initializer"); - return nil; + NSAssert(0, @"Use the designated initializer"); + return nil; } - (instancetype)initWithData:(NSMutableData *)d @@ -406,53 +406,53 @@ - (instancetype)initWithData:(NSMutableData *)d terminator:(NSData *)e tag:(long)i { - if((self = [super init])) - { - bytesDone = 0; - maxLength = m; - timeout = t; - readLength = l; - term = [e copy]; - tag = i; - - if (d) - { - buffer = d; - startOffset = s; - bufferOwner = NO; - originalBufferLength = [d length]; - } - else - { - if (readLength > 0) - buffer = [[NSMutableData alloc] initWithLength:readLength]; - else - buffer = [[NSMutableData alloc] initWithLength:0]; - - startOffset = 0; - bufferOwner = YES; - originalBufferLength = 0; - } - } - return self; + if((self = [super init])) + { + bytesDone = 0; + maxLength = m; + timeout = t; + readLength = l; + term = [e copy]; + tag = i; + + if (d) + { + buffer = d; + startOffset = s; + bufferOwner = NO; + originalBufferLength = [d length]; + } + else + { + if (readLength > 0) + buffer = [[NSMutableData alloc] initWithLength:readLength]; + else + buffer = [[NSMutableData alloc] initWithLength:0]; + + startOffset = 0; + bufferOwner = YES; + originalBufferLength = 0; + } + } + return self; } /** * Increases the length of the buffer (if needed) to ensure a read of the given size will fit. -**/ + **/ - (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; - NSUInteger buffSpace = buffSize - buffUsed; + NSUInteger buffSpace = buffSize - buffUsed; - if (bytesToRead > buffSpace) - { - NSUInteger buffInc = bytesToRead - buffSpace; + if (bytesToRead > buffSpace) + { + NSUInteger buffInc = bytesToRead - buffSpace; - [buffer increaseLengthBy:buffInc]; - } + [buffer increaseLengthBy:buffInc]; + } } /** @@ -461,60 +461,60 @@ - (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead * * Furthermore, the shouldPreBuffer decision is based upon the packet type, * and whether the returned value would fit in the current buffer without requiring a resize of the buffer. -**/ + **/ - (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr { - NSUInteger result; - - if (readLength > 0) - { - // Read a specific length of data - result = readLength - bytesDone; - - // There is no need to prebuffer since we know exactly how much data we need to read. - // Even if the buffer isn't currently big enough to fit this amount of data, - // it would have to be resized eventually anyway. - - if (shouldPreBufferPtr) - *shouldPreBufferPtr = NO; - } - else - { - // Either reading until we find a specified terminator, - // or we're simply reading all available data. - // - // In other words, one of: - // - // - readDataToData packet - // - readDataWithTimeout packet - - if (maxLength > 0) - result = MIN(defaultValue, (maxLength - bytesDone)); - else - result = defaultValue; - - // Since we don't know the size of the read in advance, - // the shouldPreBuffer decision is based upon whether the returned value would fit - // in the current buffer without requiring a resize of the buffer. - // - // This is because, in all likelyhood, the amount read from the socket will be less than the default value. - // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - NSUInteger buffSpace = buffSize - buffUsed; - - if (buffSpace >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - } - - return result; + NSUInteger result; + + if (readLength > 0) + { + // Read a specific length of data + result = readLength - bytesDone; + + // There is no need to prebuffer since we know exactly how much data we need to read. + // Even if the buffer isn't currently big enough to fit this amount of data, + // it would have to be resized eventually anyway. + + if (shouldPreBufferPtr) + *shouldPreBufferPtr = NO; + } + else + { + // Either reading until we find a specified terminator, + // or we're simply reading all available data. + // + // In other words, one of: + // + // - readDataToData packet + // - readDataWithTimeout packet + + if (maxLength > 0) + result = MIN(defaultValue, (maxLength - bytesDone)); + else + result = defaultValue; + + // Since we don't know the size of the read in advance, + // the shouldPreBuffer decision is based upon whether the returned value would fit + // in the current buffer without requiring a resize of the buffer. + // + // This is because, in all likelyhood, the amount read from the socket will be less than the default value. + // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead. + + if (shouldPreBufferPtr) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; + + NSUInteger buffSpace = buffSize - buffUsed; + + if (buffSpace >= result) + *shouldPreBufferPtr = NO; + else + *shouldPreBufferPtr = YES; + } + } + + return result; } /** @@ -525,47 +525,47 @@ - (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuf * which is taken into consideration during the calculation. * * The given hint MUST be greater than zero. -**/ + **/ - (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable { - NSAssert(term == nil, @"This method does not apply to term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); + NSAssert(term == nil, @"This method does not apply to term reads"); + NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - if (readLength > 0) - { - // Read a specific length of data + if (readLength > 0) + { + // Read a specific length of data - return MIN(bytesAvailable, (readLength - bytesDone)); + return MIN(bytesAvailable, (readLength - bytesDone)); - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read a certain length of data that exceeds the size of the buffer, - // then it is clear that our code will resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. - } - else - { - // Read all available data + // No need to avoid resizing the buffer. + // If the user provided their own buffer, + // and told us to read a certain length of data that exceeds the size of the buffer, + // then it is clear that our code will resize the buffer during the read operation. + // + // This method does not actually do any resizing. + // The resizing will happen elsewhere if needed. + } + else + { + // Read all available data - NSUInteger result = bytesAvailable; + NSUInteger result = bytesAvailable; - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } - // No need to avoid resizing the buffer. - // If the user provided their own buffer, - // and told us to read all available data without giving us a maxLength, - // then it is clear that our code might resize the buffer during the read operation. - // - // This method does not actually do any resizing. - // The resizing will happen elsewhere if needed. + // No need to avoid resizing the buffer. + // If the user provided their own buffer, + // and told us to read all available data without giving us a maxLength, + // then it is clear that our code might resize the buffer during the read operation. + // + // This method does not actually do any resizing. + // The resizing will happen elsewhere if needed. - return result; - } + return result; + } } /** @@ -578,72 +578,72 @@ - (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable * To optimize memory allocations, mem copies, and mem moves * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first, * or if the data can be read directly into the read packet's buffer. -**/ + **/ - (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr { - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); - - - NSUInteger result = bytesAvailable; - - if (maxLength > 0) - { - result = MIN(result, (maxLength - bytesDone)); - } - - // Should the data be read into the read packet's buffer, or into a pre-buffer first? - // - // One would imagine the preferred option is the faster one. - // So which one is faster? - // - // Reading directly into the packet's buffer requires: - // 1. Possibly resizing packet buffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) - // - // Reading into prebuffer first: - // 1. Possibly resizing prebuffer (malloc/realloc) - // 2. Filling buffer (read) - // 3. Searching for term (memcmp) - // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) - // 5. Removing underflow from prebuffer (memmove) - // - // Comparing the performance of the two we can see that reading - // data into the prebuffer first is slower due to the extra memove. - // - // However: - // The implementation of NSMutableData is open source via core foundation's CFMutableData. - // Decreasing the length of a mutable data object doesn't cause a realloc. - // In other words, the capacity of a mutable data object can grow, but doesn't shrink. - // - // This means the prebuffer will rarely need a realloc. - // The packet buffer, on the other hand, may often need a realloc. - // This is especially true if we are the buffer owner. - // Furthermore, if we are constantly realloc'ing the packet buffer, - // and then moving the overflow into the prebuffer, - // then we're consistently over-allocating memory for each term read. - // And now we get into a bit of a tradeoff between speed and memory utilization. - // - // The end result is that the two perform very similarly. - // And we can answer the original question very simply by another means. - // - // If we can read all the data directly into the packet's buffer without resizing it first, - // then we do so. Otherwise we use the prebuffer. - - if (shouldPreBufferPtr) - { - NSUInteger buffSize = [buffer length]; - NSUInteger buffUsed = startOffset + bytesDone; - - if ((buffSize - buffUsed) >= result) - *shouldPreBufferPtr = NO; - else - *shouldPreBufferPtr = YES; - } - - return result; + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable"); + + + NSUInteger result = bytesAvailable; + + if (maxLength > 0) + { + result = MIN(result, (maxLength - bytesDone)); + } + + // Should the data be read into the read packet's buffer, or into a pre-buffer first? + // + // One would imagine the preferred option is the faster one. + // So which one is faster? + // + // Reading directly into the packet's buffer requires: + // 1. Possibly resizing packet buffer (malloc/realloc) + // 2. Filling buffer (read) + // 3. Searching for term (memcmp) + // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy) + // + // Reading into prebuffer first: + // 1. Possibly resizing prebuffer (malloc/realloc) + // 2. Filling buffer (read) + // 3. Searching for term (memcmp) + // 4. Copying underflow into packet buffer (malloc/realloc, memcpy) + // 5. Removing underflow from prebuffer (memmove) + // + // Comparing the performance of the two we can see that reading + // data into the prebuffer first is slower due to the extra memove. + // + // However: + // The implementation of NSMutableData is open source via core foundation's CFMutableData. + // Decreasing the length of a mutable data object doesn't cause a realloc. + // In other words, the capacity of a mutable data object can grow, but doesn't shrink. + // + // This means the prebuffer will rarely need a realloc. + // The packet buffer, on the other hand, may often need a realloc. + // This is especially true if we are the buffer owner. + // Furthermore, if we are constantly realloc'ing the packet buffer, + // and then moving the overflow into the prebuffer, + // then we're consistently over-allocating memory for each term read. + // And now we get into a bit of a tradeoff between speed and memory utilization. + // + // The end result is that the two perform very similarly. + // And we can answer the original question very simply by another means. + // + // If we can read all the data directly into the packet's buffer without resizing it first, + // then we do so. Otherwise we use the prebuffer. + + if (shouldPreBufferPtr) + { + NSUInteger buffSize = [buffer length]; + NSUInteger buffUsed = startOffset + bytesDone; + + if ((buffSize - buffUsed) >= result) + *shouldPreBufferPtr = NO; + else + *shouldPreBufferPtr = YES; + } + + return result; } /** @@ -652,126 +652,126 @@ - (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuff * without going over a terminator or the maxLength. * * It is assumed the terminator has not already been read. -**/ + **/ - (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr { - NSAssert(term != nil, @"This method does not apply to non-term reads"); - NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); - - // We know that the terminator, as a whole, doesn't exist in our own buffer. - // But it is possible that a _portion_ of it exists in our buffer. - // So we're going to look for the terminator starting with a portion of our own buffer. - // - // Example: - // - // term length = 3 bytes - // bytesDone = 5 bytes - // preBuffer length = 5 bytes - // - // If we append the preBuffer to our buffer, - // it would look like this: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // --------------------- - // - // So we start our search here: - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // -------^-^-^--------- - // - // And move forwards... - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------^-^-^------- - // - // Until we find the terminator or reach the end. - // - // --------------------- - // |B|B|B|B|B|P|P|P|P|P| - // ---------------^-^-^- - - BOOL found = NO; - - NSUInteger termLength = [term length]; - NSUInteger preBufferLength = [preBuffer availableBytes]; - - if ((bytesDone + preBufferLength) < termLength) - { - // Not enough data for a full term sequence yet - return preBufferLength; - } - - NSUInteger maxPreBufferLength; - if (maxLength > 0) { - maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); - - // Note: maxLength >= termLength - } - else { - maxPreBufferLength = preBufferLength; - } + NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!"); + + // We know that the terminator, as a whole, doesn't exist in our own buffer. + // But it is possible that a _portion_ of it exists in our buffer. + // So we're going to look for the terminator starting with a portion of our own buffer. + // + // Example: + // + // term length = 3 bytes + // bytesDone = 5 bytes + // preBuffer length = 5 bytes + // + // If we append the preBuffer to our buffer, + // it would look like this: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // --------------------- + // + // So we start our search here: + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // -------^-^-^--------- + // + // And move forwards... + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------^-^-^------- + // + // Until we find the terminator or reach the end. + // + // --------------------- + // |B|B|B|B|B|P|P|P|P|P| + // ---------------^-^-^- + + BOOL found = NO; + + NSUInteger termLength = [term length]; + NSUInteger preBufferLength = [preBuffer availableBytes]; + + if ((bytesDone + preBufferLength) < termLength) + { + // Not enough data for a full term sequence yet + return preBufferLength; + } + + NSUInteger maxPreBufferLength; + if (maxLength > 0) { + maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); + + // Note: maxLength >= termLength + } + else { + maxPreBufferLength = preBufferLength; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wvla" - uint8_t seq[termLength]; + uint8_t seq[termLength]; #pragma clang diagnostic pop - const void *termBuf = [term bytes]; + const void *termBuf = [term bytes]; - NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); - uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; + NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); + uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen; - NSUInteger preLen = termLength - bufLen; - const uint8_t *pre = [preBuffer readBuffer]; + NSUInteger preLen = termLength - bufLen; + const uint8_t *pre = [preBuffer readBuffer]; - NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. + NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. - NSUInteger result = maxPreBufferLength; + NSUInteger result = maxPreBufferLength; - NSUInteger i; - for (i = 0; i < loopCount; i++) - { - if (bufLen > 0) - { - // Combining bytes from buffer and preBuffer + NSUInteger i; + for (i = 0; i < loopCount; i++) + { + if (bufLen > 0) + { + // Combining bytes from buffer and preBuffer - memcpy(seq, buf, bufLen); - memcpy(seq + bufLen, pre, preLen); + memcpy(seq, buf, bufLen); + memcpy(seq + bufLen, pre, preLen); - if (memcmp(seq, termBuf, termLength) == 0) - { - result = preLen; - found = YES; - break; - } + if (memcmp(seq, termBuf, termLength) == 0) + { + result = preLen; + found = YES; + break; + } - buf++; - bufLen--; - preLen++; - } - else - { - // Comparing directly from preBuffer + buf++; + bufLen--; + preLen++; + } + else + { + // Comparing directly from preBuffer - if (memcmp(pre, termBuf, termLength) == 0) - { - NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic + if (memcmp(pre, termBuf, termLength) == 0) + { + NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic - result = preOffset + termLength; - found = YES; - break; - } + result = preOffset + termLength; + found = YES; + break; + } - pre++; - } - } + pre++; + } + } - // There is no need to avoid resizing the buffer in this particular situation. + // There is no need to avoid resizing the buffer in this particular situation. - if (foundPtr) *foundPtr = found; - return result; + if (foundPtr) *foundPtr = found; + return result; } /** @@ -786,38 +786,38 @@ - (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffe * Prerequisites: * The given number of bytes have been added to the end of our buffer. * Our bytesDone variable has NOT been changed due to the prebuffered bytes. -**/ + **/ - (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes { - NSAssert(term != nil, @"This method does not apply to non-term reads"); + NSAssert(term != nil, @"This method does not apply to non-term reads"); - // The implementation of this method is very similar to the above method. - // See the above method for a discussion of the algorithm used here. + // The implementation of this method is very similar to the above method. + // See the above method for a discussion of the algorithm used here. - uint8_t *buff = [buffer mutableBytes]; - NSUInteger buffLength = bytesDone + numBytes; + uint8_t *buff = [buffer mutableBytes]; + NSUInteger buffLength = bytesDone + numBytes; - const void *termBuff = [term bytes]; - NSUInteger termLength = [term length]; + const void *termBuff = [term bytes]; + NSUInteger termLength = [term length]; - // Note: We are dealing with unsigned integers, - // so make sure the math doesn't go below zero. + // Note: We are dealing with unsigned integers, + // so make sure the math doesn't go below zero. - NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; + NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0; - while (i + termLength <= buffLength) - { - uint8_t *subBuffer = buff + startOffset + i; + while (i + termLength <= buffLength) + { + uint8_t *subBuffer = buff + startOffset + i; - if (memcmp(subBuffer, termBuff, termLength) == 0) - { - return buffLength - (i + termLength); - } + if (memcmp(subBuffer, termBuff, termLength) == 0) + { + return buffLength - (i + termLength); + } - i++; - } + i++; + } - return -1; + return -1; } @@ -829,14 +829,14 @@ - (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes /** * The GCDAsyncWritePacket encompasses the instructions for any given write. -**/ + **/ @interface GCDAsyncWritePacket : NSObject { - @public - NSData *buffer; - NSUInteger bytesDone; - long tag; - NSTimeInterval timeout; +@public + NSData *buffer; + NSUInteger bytesDone; + long tag; + NSTimeInterval timeout; } - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; @end @@ -846,20 +846,20 @@ @implementation GCDAsyncWritePacket // Cover the superclass' designated initializer - (instancetype)init NS_UNAVAILABLE { - NSAssert(0, @"Use the designated initializer"); - return nil; + NSAssert(0, @"Use the designated initializer"); + return nil; } - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i { - if((self = [super init])) - { - buffer = d; // Retain not copy. For performance as documented in header file. - bytesDone = 0; - timeout = t; - tag = i; - } - return self; + if((self = [super init])) + { + buffer = d; // Retain not copy. For performance as documented in header file. + bytesDone = 0; + timeout = t; + tag = i; + } + return self; } @@ -872,13 +872,14 @@ - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i /** * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. * This class my be altered to support more than just TLS in the future. -**/ + **/ @interface GCDAsyncSpecialPacket : NSObject { - @public - NSDictionary *tlsSettings; +@public + NSDictionary *tlsSettings; } -- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER; @end @implementation GCDAsyncSpecialPacket @@ -886,17 +887,18 @@ @implementation GCDAsyncSpecialPacket // Cover the superclass' designated initializer - (instancetype)init NS_UNAVAILABLE { - NSAssert(0, @"Use the designated initializer"); - return nil; + NSAssert(0, @"Use the designated initializer"); + return nil; } -- (instancetype)initWithTLSSettings:(NSDictionary *)settings +- (instancetype)initWithTLSSettings:(NSDictionary *)settings { - if((self = [super init])) - { - tlsSettings = [settings copy]; - } - return self; + if((self = [super init])) + { + tlsSettings = [settings copy]; + } + return self; } @@ -908,176 +910,181 @@ - (instancetype)initWithTLSSettings:(NSDictionary *)setting @implementation GCDAsyncSocket { - uint32_t flags; - uint16_t config; + uint32_t flags; + uint16_t config; - __weak id delegate; - dispatch_queue_t delegateQueue; + __weak id delegate; + dispatch_queue_t delegateQueue; - int socket4FD; - int socket6FD; - int socketUN; - NSURL *socketUrl; - int stateIndex; - NSData * connectInterface4; - NSData * connectInterface6; - NSData * connectInterfaceUN; + int socket4FD; + int socket6FD; + int socketUN; + NSURL *socketUrl; + int stateIndex; + NSData * connectInterface4; + NSData * connectInterface6; + NSData * connectInterfaceUN; - dispatch_queue_t socketQueue; + dispatch_queue_t socketQueue; - dispatch_source_t accept4Source; - dispatch_source_t accept6Source; - dispatch_source_t acceptUNSource; - dispatch_source_t connectTimer; - dispatch_source_t readSource; - dispatch_source_t writeSource; - dispatch_source_t readTimer; - dispatch_source_t writeTimer; + dispatch_source_t accept4Source; + dispatch_source_t accept6Source; + dispatch_source_t acceptUNSource; + dispatch_source_t connectTimer; + dispatch_source_t readSource; + dispatch_source_t writeSource; + dispatch_source_t readTimer; + dispatch_source_t writeTimer; - NSMutableArray *readQueue; - NSMutableArray *writeQueue; + NSMutableArray *readQueue; + NSMutableArray *writeQueue; - GCDAsyncReadPacket *currentRead; - GCDAsyncWritePacket *currentWrite; + GCDAsyncReadPacket *currentRead; + GCDAsyncWritePacket *currentWrite; - unsigned long socketFDBytesAvailable; + unsigned long socketFDBytesAvailable; - GCDAsyncSocketPreBuffer *preBuffer; + GCDAsyncSocketPreBuffer *preBuffer; #if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream; - CFWriteStreamRef writeStream; + CFStreamClientContext streamContext; + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; #endif - SSLContextRef sslContext; - GCDAsyncSocketPreBuffer *sslPreBuffer; - size_t sslWriteCachedLength; - OSStatus sslErrCode; - OSStatus lastSSLHandshakeError; + SSLContextRef sslContext; + GCDAsyncSocketPreBuffer *sslPreBuffer; + size_t sslWriteCachedLength; + OSStatus sslErrCode; + OSStatus lastSSLHandshakeError; - void *IsOnSocketQueueOrTargetQueueKey; + void *IsOnSocketQueueOrTargetQueueKey; - id userData; - NSTimeInterval alternateAddressDelay; + id userData; + NSTimeInterval alternateAddressDelay; } - (instancetype)init { - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; } - (instancetype)initWithSocketQueue:(dispatch_queue_t)sq { - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; } - (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq { - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; + return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; } - (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq { - if((self = [super init])) - { - delegate = aDelegate; - delegateQueue = dq; - - #if !OS_OBJECT_USE_OBJC - if (dq) dispatch_retain(dq); - #endif - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - socketUrl = nil; - stateIndex = 0; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(sq); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); - - readQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentRead = nil; - - writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; - currentWrite = nil; - - preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - alternateAddressDelay = 0.3; - } - return self; + if((self = [super init])) + { + delegate = aDelegate; + delegateQueue = dq; + +#if !OS_OBJECT_USE_OBJC + if (dq) dispatch_retain(dq); +#endif + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + socketUN = SOCKET_NULL; + socketUrl = nil; + stateIndex = 0; + + if (sq) + { + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0), + @"The given socketQueue parameter must not be a concurrent queue."); + + socketQueue = sq; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(sq); +#endif + } + else + { + socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], + NULL); + } + + // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. + // From the documentation: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // + // We're just going to use the memory address of an ivar. + // Specifically an ivar that is explicitly named for our purpose to make the code more readable. + // + // However, it feels tedious (and less readable) to include the "&" all the time: + // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) + // + // So we're going to make it so it doesn't matter if we use the '&' or not, + // by assigning the value of the ivar to the address of the ivar. + // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; + + IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketQueue, + IsOnSocketQueueOrTargetQueueKey, + nonNullUnusedPointer, + NULL); + + readQueue = [[NSMutableArray alloc] initWithCapacity:5]; + currentRead = nil; + + writeQueue = [[NSMutableArray alloc] initWithCapacity:5]; + currentWrite = nil; + + preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; + alternateAddressDelay = 0.3; + } + return self; } - (void)dealloc { - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); - // Set dealloc flag. - // This is used by closeWithError to ensure we don't accidentally retain ourself. - flags |= kDealloc; + // Set dealloc flag. + // This is used by closeWithError to ensure we don't accidentally retain ourself. + flags |= kDealloc; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + [self closeWithError:nil]; + } + else + { + dispatch_sync(socketQueue, ^{ + [self closeWithError:nil]; + }); + } - delegate = nil; + delegate = nil; - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; +#if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); +#endif + delegateQueue = NULL; - #if !OS_OBJECT_USE_OBJC - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; +#if !OS_OBJECT_USE_OBJC + if (socketQueue) dispatch_release(socketQueue); +#endif + socketQueue = NULL; - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); + LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); } #pragma mark - @@ -1095,15 +1102,18 @@ + (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nul GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq]; __block NSError *innerError = nil; - dispatch_sync(socket->socketQueue, ^{ @autoreleasepool { + dispatch_sync(socket->socketQueue, + ^{ @autoreleasepool { struct sockaddr addr; socklen_t addr_size = sizeof(struct sockaddr); int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size); if (retVal) { NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to create socket from socket FD failed. getpeername() failed", nil); + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Attempt to create socket from socket FD failed. getpeername() failed", + nil); NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; @@ -1124,8 +1134,10 @@ + (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nul else { NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", nil); + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", + nil); NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; @@ -1151,324 +1163,324 @@ + (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nul - (id)delegate { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegate; + } + else + { + __block id result; - dispatch_sync(socketQueue, ^{ - result = self->delegate; - }); + dispatch_sync(socketQueue, ^{ + result = self->delegate; + }); - return result; - } + return result; + } } - (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ - self->delegate = newDelegate; - }; + dispatch_block_t block = ^{ + self->delegate = newDelegate; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegate:(id)newDelegate { - [self setDelegate:newDelegate synchronously:NO]; + [self setDelegate:newDelegate synchronously:NO]; } - (void)synchronouslySetDelegate:(id)newDelegate { - [self setDelegate:newDelegate synchronously:YES]; + [self setDelegate:newDelegate synchronously:YES]; } - (dispatch_queue_t)delegateQueue { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegateQueue; + } + else + { + __block dispatch_queue_t result; - dispatch_sync(socketQueue, ^{ - result = self->delegateQueue; - }); + dispatch_sync(socketQueue, ^{ + result = self->delegateQueue; + }); - return result; - } + return result; + } } - (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - #if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); +#endif - self->delegateQueue = newDelegateQueue; - }; + self->delegateQueue = newDelegateQueue; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegateQueue:newDelegateQueue synchronously:NO]; + [self setDelegateQueue:newDelegateQueue synchronously:NO]; } - (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegateQueue:newDelegateQueue synchronously:YES]; + [self setDelegateQueue:newDelegateQueue synchronously:YES]; } - (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; - - dispatch_sync(socketQueue, ^{ - dPtr = self->delegate; - dqPtr = self->delegateQueue; - }); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (delegatePtr) *delegatePtr = delegate; + if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; + } + else + { + __block id dPtr = NULL; + __block dispatch_queue_t dqPtr = NULL; + + dispatch_sync(socketQueue, ^{ + dPtr = self->delegate; + dqPtr = self->delegateQueue; + }); - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } + if (delegatePtr) *delegatePtr = dPtr; + if (delegateQueuePtr) *delegateQueuePtr = dqPtr; + } } - (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - self->delegate = newDelegate; + self->delegate = newDelegate; - #if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); +#endif - self->delegateQueue = newDelegateQueue; - }; + self->delegateQueue = newDelegateQueue; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; } - (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; } - (BOOL)isIPv4Enabled { - // Note: YES means kIPv4Disabled is OFF + // Note: YES means kIPv4Disabled is OFF - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv4Disabled) == 0); - } - else - { - __block BOOL result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kIPv4Disabled) == 0); + } + else + { + __block BOOL result; - dispatch_sync(socketQueue, ^{ - result = ((self->config & kIPv4Disabled) == 0); - }); + dispatch_sync(socketQueue, ^{ + result = ((self->config & kIPv4Disabled) == 0); + }); - return result; - } + return result; + } } - (void)setIPv4Enabled:(BOOL)flag { - // Note: YES means kIPv4Disabled is OFF + // Note: YES means kIPv4Disabled is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (flag) - self->config &= ~kIPv4Disabled; - else - self->config |= kIPv4Disabled; - }; + if (flag) + self->config &= ~kIPv4Disabled; + else + self->config |= kIPv4Disabled; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (BOOL)isIPv6Enabled { - // Note: YES means kIPv6Disabled is OFF + // Note: YES means kIPv6Disabled is OFF - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kIPv6Disabled) == 0); - } - else - { - __block BOOL result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kIPv6Disabled) == 0); + } + else + { + __block BOOL result; - dispatch_sync(socketQueue, ^{ - result = ((self->config & kIPv6Disabled) == 0); - }); + dispatch_sync(socketQueue, ^{ + result = ((self->config & kIPv6Disabled) == 0); + }); - return result; - } + return result; + } } - (void)setIPv6Enabled:(BOOL)flag { - // Note: YES means kIPv6Disabled is OFF + // Note: YES means kIPv6Disabled is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (flag) - self->config &= ~kIPv6Disabled; - else - self->config |= kIPv6Disabled; - }; + if (flag) + self->config &= ~kIPv6Disabled; + else + self->config |= kIPv6Disabled; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (BOOL)isIPv4PreferredOverIPv6 { - // Note: YES means kPreferIPv6 is OFF + // Note: YES means kPreferIPv6 is OFF - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kPreferIPv6) == 0); - } - else - { - __block BOOL result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kPreferIPv6) == 0); + } + else + { + __block BOOL result; - dispatch_sync(socketQueue, ^{ - result = ((self->config & kPreferIPv6) == 0); - }); + dispatch_sync(socketQueue, ^{ + result = ((self->config & kPreferIPv6) == 0); + }); - return result; - } + return result; + } } - (void)setIPv4PreferredOverIPv6:(BOOL)flag { - // Note: YES means kPreferIPv6 is OFF + // Note: YES means kPreferIPv6 is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (flag) - self->config &= ~kPreferIPv6; - else - self->config |= kPreferIPv6; - }; + if (flag) + self->config &= ~kPreferIPv6; + else + self->config |= kPreferIPv6; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (NSTimeInterval) alternateAddressDelay { - __block NSTimeInterval delay; - dispatch_block_t block = ^{ - delay = self->alternateAddressDelay; - }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); - return delay; + __block NSTimeInterval delay; + dispatch_block_t block = ^{ + delay = self->alternateAddressDelay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + return delay; } - (void) setAlternateAddressDelay:(NSTimeInterval)delay { - dispatch_block_t block = ^{ - self->alternateAddressDelay = delay; - }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + dispatch_block_t block = ^{ + self->alternateAddressDelay = delay; + }; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (id)userData { - __block id result = nil; + __block id result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = self->userData; - }; + result = self->userData; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setUserData:(id)arbitraryUserData { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->userData != arbitraryUserData) - { - self->userData = arbitraryUserData; - } - }; + if (self->userData != arbitraryUserData) + { + self->userData = arbitraryUserData; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1477,666 +1489,696 @@ - (void)setUserData:(id)arbitraryUserData - (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr { - return [self acceptOnInterface:nil port:port error:errPtr]; + return [self acceptOnInterface:nil port:port error:errPtr]; } - (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - // Just in-case interface parameter is immutable. - NSString *interface = [inInterface copy]; + // Just in-case interface parameter is immutable. + NSString *interface = [inInterface copy]; - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - // CreateSocket Block - // This block will be invoked within the dispatch block below. + // CreateSocket Block + // This block will be invoked within the dispatch block below. - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { + int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - int socketFD = socket(domain, SOCK_STREAM, 0); + int socketFD = socket(domain, SOCK_STREAM, 0); - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errorWithErrno:errno reason:reason]; + if (socketFD == SOCKET_NULL) + { + NSString *reason = @"Error in socket() function"; + err = [self errorWithErrno:errno reason:reason]; - return SOCKET_NULL; - } + return SOCKET_NULL; + } - int status; + int status; - // Set socket options + // Set socket options - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errorWithErrno:errno reason:reason]; + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - int reuseOn = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errorWithErrno:errno reason:reason]; + int reuseOn = 1; + status = setsockopt(socketFD, + SOL_SOCKET, + SO_REUSEADDR, + &reuseOn, + sizeof(reuseOn)); + if (status == -1) + { + NSString *reason = @"Error enabling address reuse (setsockopt)"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - // Bind socket + // Bind socket - status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errorWithErrno:errno reason:reason]; + status = bind(socketFD, + (const struct sockaddr *)[interfaceAddr bytes], + (socklen_t)[interfaceAddr length]); + if (status == -1) + { + NSString *reason = @"Error in bind() function"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - // Listen + // Listen - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errorWithErrno:errno reason:reason]; + status = listen(socketFD, 1024); + if (status == -1) + { + NSString *reason = @"Error in listen() function"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - return socketFD; - }; + return socketFD; + }; - // Create dispatch block and run on socketQueue + // Create dispatch block and run on socketQueue - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (self->delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; + if (self->delegate == nil) // Must have delegate set + { + NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - if (self->delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; + if (self->delegateQueue == NULL) // Must have delegate queue set + { + NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - err = [self badConfigError:msg]; + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; + if (![self isDisconnected]) // Must be disconnected + { + NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - // Clear queues (spurious read/write requests post disconnect) - [self->readQueue removeAllObjects]; - [self->writeQueue removeAllObjects]; + // Clear queues (spurious read/write requests post disconnect) + [self->readQueue removeAllObjects]; + [self->writeQueue removeAllObjects]; - // Resolve interface from description + // Resolve interface from description - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; + NSMutableData *interface4 = nil; + NSMutableData *interface6 = nil; - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; + [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port]; - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; + if ((interface4 == nil) && (interface6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; + if (isIPv4Disabled && (interface6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; + if (isIPv6Disabled && (interface4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); + BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil); + BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil); - // Create sockets, configure, bind, and listen + // Create sockets, configure, bind, and listen - if (enableIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - self->socket4FD = createSocket(AF_INET, interface4); + if (enableIPv4) + { + LogVerbose(@"Creating IPv4 socket"); + self->socket4FD = createSocket(AF_INET, interface4); - if (self->socket4FD == SOCKET_NULL) - { - return_from_block; - } - } + if (self->socket4FD == SOCKET_NULL) + { + return_from_block; + } + } - if (enableIPv6) - { - LogVerbose(@"Creating IPv6 socket"); + if (enableIPv6) + { + LogVerbose(@"Creating IPv6 socket"); - if (enableIPv4 && (port == 0)) - { - // No specific port was specified, so we allowed the OS to pick an available port for us. - // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. + if (enableIPv4 && (port == 0)) + { + // No specific port was specified, so we allowed the OS to pick an available port for us. + // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket. - struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; - addr6->sin6_port = htons([self localPort4]); - } + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes]; + addr6->sin6_port = htons([self localPort4]); + } - self->socket6FD = createSocket(AF_INET6, interface6); + self->socket6FD = createSocket(AF_INET6, interface6); - if (self->socket6FD == SOCKET_NULL) - { - if (self->socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(self->socket4FD); - self->socket4FD = SOCKET_NULL; - } + if (self->socket6FD == SOCKET_NULL) + { + if (self->socket4FD != SOCKET_NULL) + { + LogVerbose(@"close(socket4FD)"); + close(self->socket4FD); + self->socket4FD = SOCKET_NULL; + } - return_from_block; - } - } + return_from_block; + } + } - // Create accept sources + // Create accept sources - if (enableIPv4) - { - self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket4FD, 0, self->socketQueue); + if (enableIPv4) + { + self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + self->socket4FD, + 0, + self->socketQueue); - int socketFD = self->socket4FD; - dispatch_source_t acceptSource = self->accept4Source; + int socketFD = self->socket4FD; + dispatch_source_t acceptSource = self->accept4Source; - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_source_set_event_handler(self->accept4Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_event_handler(self->accept4Source, + ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - LogVerbose(@"event4Block"); + LogVerbose(@"event4Block"); - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - dispatch_source_set_cancel_handler(self->accept4Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_cancel_handler(self->accept4Source, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept4Source)"); - dispatch_release(acceptSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(accept4Source)"); + dispatch_release(acceptSource); +#endif - LogVerbose(@"close(socket4FD)"); - close(socketFD); + LogVerbose(@"close(socket4FD)"); + close(socketFD); - #pragma clang diagnostic pop - }); +#pragma clang diagnostic pop + }); - LogVerbose(@"dispatch_resume(accept4Source)"); - dispatch_resume(self->accept4Source); - } + LogVerbose(@"dispatch_resume(accept4Source)"); + dispatch_resume(self->accept4Source); + } - if (enableIPv6) - { - self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket6FD, 0, self->socketQueue); + if (enableIPv6) + { + self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + self->socket6FD, + 0, + self->socketQueue); - int socketFD = self->socket6FD; - dispatch_source_t acceptSource = self->accept6Source; + int socketFD = self->socket6FD; + dispatch_source_t acceptSource = self->accept6Source; - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_source_set_event_handler(self->accept6Source, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_event_handler(self->accept6Source, + ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - LogVerbose(@"event6Block"); + LogVerbose(@"event6Block"); - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - dispatch_source_set_cancel_handler(self->accept6Source, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_cancel_handler(self->accept6Source, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(accept6Source)"); - dispatch_release(acceptSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(accept6Source)"); + dispatch_release(acceptSource); +#endif - LogVerbose(@"close(socket6FD)"); - close(socketFD); + LogVerbose(@"close(socket6FD)"); + close(socketFD); - #pragma clang diagnostic pop - }); +#pragma clang diagnostic pop + }); - LogVerbose(@"dispatch_resume(accept6Source)"); - dispatch_resume(self->accept6Source); - } + LogVerbose(@"dispatch_resume(accept6Source)"); + dispatch_resume(self->accept6Source); + } - self->flags |= kSocketStarted; + self->flags |= kSocketStarted; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); + if (result == NO) + { + LogInfo(@"Error in accept: %@", err); - if (errPtr) - *errPtr = err; - } + if (errPtr) + *errPtr = err; + } - return result; + return result; } - (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - // CreateSocket Block - // This block will be invoked within the dispatch block below. + // CreateSocket Block + // This block will be invoked within the dispatch block below. - int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { + int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) { - int socketFD = socket(domain, SOCK_STREAM, 0); + int socketFD = socket(domain, SOCK_STREAM, 0); - if (socketFD == SOCKET_NULL) - { - NSString *reason = @"Error in socket() function"; - err = [self errorWithErrno:errno reason:reason]; + if (socketFD == SOCKET_NULL) + { + NSString *reason = @"Error in socket() function"; + err = [self errorWithErrno:errno reason:reason]; - return SOCKET_NULL; - } + return SOCKET_NULL; + } - int status; + int status; - // Set socket options + // Set socket options - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; - err = [self errorWithErrno:errno reason:reason]; + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - int reuseOn = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - if (status == -1) - { - NSString *reason = @"Error enabling address reuse (setsockopt)"; - err = [self errorWithErrno:errno reason:reason]; + int reuseOn = 1; + status = setsockopt(socketFD, + SOL_SOCKET, + SO_REUSEADDR, + &reuseOn, + sizeof(reuseOn)); + if (status == -1) + { + NSString *reason = @"Error enabling address reuse (setsockopt)"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - // Bind socket + // Bind socket - status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]); - if (status == -1) - { - NSString *reason = @"Error in bind() function"; - err = [self errorWithErrno:errno reason:reason]; + status = bind(socketFD, + (const struct sockaddr *)[interfaceAddr bytes], + (socklen_t)[interfaceAddr length]); + if (status == -1) + { + NSString *reason = @"Error in bind() function"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - // Listen + // Listen - status = listen(socketFD, 1024); - if (status == -1) - { - NSString *reason = @"Error in listen() function"; - err = [self errorWithErrno:errno reason:reason]; + status = listen(socketFD, 1024); + if (status == -1) + { + NSString *reason = @"Error in listen() function"; + err = [self errorWithErrno:errno reason:reason]; - LogVerbose(@"close(socketFD)"); - close(socketFD); - return SOCKET_NULL; - } + LogVerbose(@"close(socketFD)"); + close(socketFD); + return SOCKET_NULL; + } - return socketFD; - }; + return socketFD; + }; - // Create dispatch block and run on socketQueue + // Create dispatch block and run on socketQueue - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (self->delegate == nil) // Must have delegate set - { - NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; - err = [self badConfigError:msg]; + if (self->delegate == nil) // Must have delegate set + { + NSString *msg = @"Attempting to accept without a delegate. Set a delegate first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - if (self->delegateQueue == NULL) // Must have delegate queue set - { - NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; - err = [self badConfigError:msg]; + if (self->delegateQueue == NULL) // Must have delegate queue set + { + NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - if (![self isDisconnected]) // Must be disconnected - { - NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; - err = [self badConfigError:msg]; + if (![self isDisconnected]) // Must be disconnected + { + NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first."; + err = [self badConfigError:msg]; - return_from_block; - } + return_from_block; + } - // Clear queues (spurious read/write requests post disconnect) - [self->readQueue removeAllObjects]; - [self->writeQueue removeAllObjects]; + // Clear queues (spurious read/write requests post disconnect) + [self->readQueue removeAllObjects]; + [self->writeQueue removeAllObjects]; - // Remove a previous socket + // Remove a previous socket - NSError *error = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *urlPath = url.path; - if (urlPath && [fileManager fileExistsAtPath:urlPath]) { - if (![fileManager removeItemAtURL:url error:&error]) { - NSString *msg = @"Could not remove previous unix domain socket at given url."; - err = [self otherError:msg]; + NSError *error = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *urlPath = url.path; + if (urlPath && [fileManager fileExistsAtPath:urlPath]) { + if (![fileManager removeItemAtURL:url error:&error]) { + NSString *msg = @"Could not remove previous unix domain socket at given url."; + err = [self otherError:msg]; - return_from_block; - } - } + return_from_block; + } + } - // Resolve interface from description + // Resolve interface from description - NSData *interface = [self getInterfaceAddressFromUrl:url]; + NSData *interface = [self getInterfaceAddressFromUrl:url]; - if (interface == nil) - { - NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; - err = [self badParamError:msg]; + if (interface == nil) + { + NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")"; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Create sockets, configure, bind, and listen + // Create sockets, configure, bind, and listen - LogVerbose(@"Creating unix domain socket"); - self->socketUN = createSocket(AF_UNIX, interface); + LogVerbose(@"Creating unix domain socket"); + self->socketUN = createSocket(AF_UNIX, interface); - if (self->socketUN == SOCKET_NULL) - { - return_from_block; - } + if (self->socketUN == SOCKET_NULL) + { + return_from_block; + } - self->socketUrl = url; + self->socketUrl = url; - // Create accept sources + // Create accept sources - self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socketUN, 0, self->socketQueue); + self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + self->socketUN, + 0, + self->socketQueue); - int socketFD = self->socketUN; - dispatch_source_t acceptSource = self->acceptUNSource; + int socketFD = self->socketUN; + dispatch_source_t acceptSource = self->acceptUNSource; - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_source_set_event_handler(self->acceptUNSource, ^{ @autoreleasepool { + dispatch_source_set_event_handler(self->acceptUNSource, + ^{ @autoreleasepool { - __strong GCDAsyncSocket *strongSelf = weakSelf; + __strong GCDAsyncSocket *strongSelf = weakSelf; - LogVerbose(@"eventUNBlock"); + LogVerbose(@"eventUNBlock"); - unsigned long i = 0; - unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); + unsigned long i = 0; + unsigned long numPendingConnections = dispatch_source_get_data(acceptSource); - LogVerbose(@"numPendingConnections: %lu", numPendingConnections); + LogVerbose(@"numPendingConnections: %lu", numPendingConnections); - while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); - }}); + while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections)); + }}); - dispatch_source_set_cancel_handler(self->acceptUNSource, ^{ + dispatch_source_set_cancel_handler(self->acceptUNSource, ^{ #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(acceptUNSource)"); - dispatch_release(acceptSource); + LogVerbose(@"dispatch_release(acceptUNSource)"); + dispatch_release(acceptSource); #endif - LogVerbose(@"close(socketUN)"); - close(socketFD); - }); + LogVerbose(@"close(socketUN)"); + close(socketFD); + }); - LogVerbose(@"dispatch_resume(acceptUNSource)"); - dispatch_resume(self->acceptUNSource); + LogVerbose(@"dispatch_resume(acceptUNSource)"); + dispatch_resume(self->acceptUNSource); - self->flags |= kSocketStarted; + self->flags |= kSocketStarted; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (result == NO) - { - LogInfo(@"Error in accept: %@", err); + if (result == NO) + { + LogInfo(@"Error in accept: %@", err); - if (errPtr) - *errPtr = err; - } + if (errPtr) + *errPtr = err; + } - return result; + return result; } - (BOOL)doAccept:(int)parentSocketFD { - LogTrace(); + LogTrace(); - int socketType; - int childSocketFD; - NSData *childSocketAddress; + int socketType; + int childSocketFD; + NSData *childSocketAddress; - if (parentSocketFD == socket4FD) - { - socketType = 0; + if (parentSocketFD == socket4FD) + { + socketType = 0; - struct sockaddr_in addr; - socklen_t addrLen = sizeof(addr); + struct sockaddr_in addr; + socklen_t addrLen = sizeof(addr); - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else if (parentSocketFD == socket6FD) - { - socketType = 1; + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } + else if (parentSocketFD == socket6FD) + { + socketType = 1; - struct sockaddr_in6 addr; - socklen_t addrLen = sizeof(addr); + struct sockaddr_in6 addr; + socklen_t addrLen = sizeof(addr); - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } - else // if (parentSocketFD == socketUN) - { - socketType = 2; + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } + else // if (parentSocketFD == socketUN) + { + socketType = 2; - struct sockaddr_un addr; - socklen_t addrLen = sizeof(addr); + struct sockaddr_un addr; + socklen_t addrLen = sizeof(addr); - childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); + childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen); - if (childSocketFD == -1) - { - LogWarn(@"Accept failed with error: %@", [self errnoError]); - return NO; - } + if (childSocketFD == -1) + { + LogWarn(@"Accept failed with error: %@", [self errnoError]); + return NO; + } - childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; - } + childSocketAddress = [NSData dataWithBytes:&addr length:addrLen]; + } - // Enable non-blocking IO on the socket + // Enable non-blocking IO on the socket - int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); - LogVerbose(@"close(childSocketFD)"); - close(childSocketFD); - return NO; - } + int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK); + if (result == -1) + { + LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)"); + LogVerbose(@"close(childSocketFD)"); + close(childSocketFD); + return NO; + } - // Prevent SIGPIPE signals + // Prevent SIGPIPE signals - int nosigpipe = 1; - setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + int nosigpipe = 1; + setsockopt(childSocketFD, + SOL_SOCKET, + SO_NOSIGPIPE, + &nosigpipe, + sizeof(nosigpipe)); - // Notify delegate + // Notify delegate - if (delegateQueue) - { - __strong id theDelegate = delegate; + if (delegateQueue) + { + __strong id theDelegate = delegate; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(delegateQueue, + ^{ @autoreleasepool { - // Query delegate for custom socket queue + // Query delegate for custom socket queue - dispatch_queue_t childSocketQueue = NULL; + dispatch_queue_t childSocketQueue = NULL; - if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) - { - childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress - onSocket:self]; - } + if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)]) + { + childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress + onSocket:self]; + } - // Create GCDAsyncSocket instance for accepted socket + // Create GCDAsyncSocket instance for accepted socket - GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate - delegateQueue:self->delegateQueue - socketQueue:childSocketQueue]; + GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate + delegateQueue:self->delegateQueue + socketQueue:childSocketQueue]; - if (socketType == 0) - acceptedSocket->socket4FD = childSocketFD; - else if (socketType == 1) - acceptedSocket->socket6FD = childSocketFD; - else - acceptedSocket->socketUN = childSocketFD; + if (socketType == 0) + acceptedSocket->socket4FD = childSocketFD; + else if (socketType == 1) + acceptedSocket->socket6FD = childSocketFD; + else + acceptedSocket->socketUN = childSocketFD; - acceptedSocket->flags = (kSocketStarted | kConnected); + acceptedSocket->flags = (kSocketStarted | kConnected); - // Setup read and write sources for accepted socket + // Setup read and write sources for accepted socket - dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool { + dispatch_async(acceptedSocket->socketQueue, + ^{ @autoreleasepool { - [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; - }}); + [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD]; + }}); - // Notify delegate + // Notify delegate - if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) - { - [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; - } + if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)]) + { + [theDelegate socket:self didAcceptNewSocket:acceptedSocket]; + } - // Release the socket queue returned from the delegate (it was retained by acceptedSocket) - #if !OS_OBJECT_USE_OBJC - if (childSocketQueue) dispatch_release(childSocketQueue); - #endif + // Release the socket queue returned from the delegate (it was retained by acceptedSocket) +#if !OS_OBJECT_USE_OBJC + if (childSocketQueue) dispatch_release(childSocketQueue); +#endif - // The accepted socket should have been retained by the delegate. - // Otherwise it gets properly released when exiting the block. - }}); - } + // The accepted socket should have been retained by the delegate. + // Otherwise it gets properly released when exiting the block. + }}); + } - return YES; + return YES; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2147,287 +2189,292 @@ - (BOOL)doAccept:(int)parentSocketFD * This method runs through the various checks required prior to a connection attempt. * It is shared between the connectToHost and connectToAddress methods. * -**/ + **/ - (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (interface) - { - NSMutableData *interface4 = nil; - NSMutableData *interface6 = nil; - - [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - - if ((interface4 == nil) && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv4Disabled && (interface6 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - if (isIPv6Disabled && (interface4 == nil)) - { - if (errPtr) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterface4 = interface4; - connectInterface6 = interface6; - } - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} - -- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (![self isDisconnected]) // Must be disconnected - { - if (errPtr) - { - NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - NSData *interface = [self getInterfaceAddressFromUrl:url]; - - if (interface == nil) - { - if (errPtr) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - *errPtr = [self badParamError:msg]; - } - return NO; - } - - connectInterfaceUN = interface; - - // Clear queues (spurious read/write requests post disconnect) - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - return YES; -} + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); -- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; -} + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } -- (BOOL)connectToHost:(NSString *)host - onPort:(uint16_t)port - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; -} + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } -- (BOOL)connectToHost:(NSString *)inHost - onPort:(uint16_t)port - viaInterface:(NSString *)inInterface - withTimeout:(NSTimeInterval)timeout - error:(NSError **)errPtr -{ - LogTrace(); + if (![self isDisconnected]) // Must be disconnected + { + if (errPtr) + { + NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } - // Just in case immutable objects were passed - NSString *host = [inHost copy]; - NSString *interface = [inInterface copy]; + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - __block BOOL result = NO; - __block NSError *preConnectErr = nil; + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } - dispatch_block_t block = ^{ @autoreleasepool { + if (interface) + { + NSMutableData *interface4 = nil; + NSMutableData *interface6 = nil; - // Check for problems with host parameter + [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0]; - if ([host length] == 0) - { - NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; - preConnectErr = [self badParamError:msg]; + if ((interface4 == nil) && (interface6 == nil)) + { + if (errPtr) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + *errPtr = [self badParamError:msg]; + } + return NO; + } - return_from_block; - } + if (isIPv4Disabled && (interface6 == nil)) + { + if (errPtr) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + *errPtr = [self badParamError:msg]; + } + return NO; + } - // Run through standard pre-connect checks + if (isIPv6Disabled && (interface4 == nil)) + { + if (errPtr) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + *errPtr = [self badParamError:msg]; + } + return NO; + } - if (![self preConnectWithInterface:interface error:&preConnectErr]) - { - return_from_block; - } + connectInterface4 = interface4; + connectInterface6 = interface6; + } - // We've made it past all the checks. - // It's time to start the connection process. + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; - self->flags |= kSocketStarted; + return YES; +} - LogVerbose(@"Dispatching DNS lookup..."); +- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - // It's possible that the given host parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } - NSString *hostCpy = [host copy]; + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (![self isDisconnected]) // Must be disconnected + { + if (errPtr) + { + NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + NSData *interface = [self getInterfaceAddressFromUrl:url]; + + if (interface == nil) + { + if (errPtr) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + *errPtr = [self badParamError:msg]; + } + return NO; + } + + connectInterfaceUN = interface; + + // Clear queues (spurious read/write requests post disconnect) + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; + + return YES; +} + +- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr +{ + return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr]; +} + +- (BOOL)connectToHost:(NSString *)host + onPort:(uint16_t)port + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr]; +} + +- (BOOL)connectToHost:(NSString *)inHost + onPort:(uint16_t)port + viaInterface:(NSString *)inInterface + withTimeout:(NSTimeInterval)timeout + error:(NSError **)errPtr +{ + LogTrace(); + + // Just in case immutable objects were passed + NSString *host = [inHost copy]; + NSString *interface = [inInterface copy]; + + __block BOOL result = NO; + __block NSError *preConnectErr = nil; + + dispatch_block_t block = ^{ @autoreleasepool { + + // Check for problems with host parameter + + if ([host length] == 0) + { + NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string."; + preConnectErr = [self badParamError:msg]; + + return_from_block; + } + + // Run through standard pre-connect checks + + if (![self preConnectWithInterface:interface error:&preConnectErr]) + { + return_from_block; + } + + // We've made it past all the checks. + // It's time to start the connection process. + + self->flags |= kSocketStarted; - int aStateIndex = self->stateIndex; - __weak GCDAsyncSocket *weakSelf = self; + LogVerbose(@"Dispatching DNS lookup..."); - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + // It's possible that the given host parameter is actually a NSMutableString. + // So we want to copy it now, within this block that will be executed synchronously. + // This way the asynchronous lookup block below doesn't have to worry about it changing. - NSError *lookupErr = nil; - NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; + NSString *hostCpy = [host copy]; - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + int aStateIndex = self->stateIndex; + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); + dispatch_async(globalConcurrentQueue, + ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - if (lookupErr) - { - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + NSError *lookupErr = nil; + NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr]; - [strongSelf lookup:aStateIndex didFail:lookupErr]; - }}); - } - else - { - NSData *address4 = nil; - NSData *address6 = nil; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + if (lookupErr) + { + dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + + [strongSelf lookup:aStateIndex didFail:lookupErr]; + }}); + } + else + { + NSData *address4 = nil; + NSData *address6 = nil; - for (NSData *address in addresses) - { - if (!address4 && [[self class] isIPv4Address:address]) - { - address4 = address; - } - else if (!address6 && [[self class] isIPv6Address:address]) - { - address6 = address; - } - } + for (NSData *address in addresses) + { + if (!address4 && [[self class] isIPv4Address:address]) + { + address4 = address; + } + else if (!address6 && [[self class] isIPv6Address:address]) + { + address6 = address; + } + } - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + dispatch_async(strongSelf->socketQueue, + ^{ @autoreleasepool { - [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; - }}); - } + [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6]; + }}); + } - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - [self startConnectTimeout:timeout]; + [self startConnectTimeout:timeout]; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) *errPtr = preConnectErr; - return result; + if (errPtr) *errPtr = preConnectErr; + return result; } - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr { - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; + return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr]; } - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr { - return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; + return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr]; } - (BOOL)connectToAddress:(NSData *)inRemoteAddr @@ -2435,209 +2482,210 @@ - (BOOL)connectToAddress:(NSData *)inRemoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - // Just in case immutable objects were passed - NSData *remoteAddr = [inRemoteAddr copy]; - NSString *interface = [inInterface copy]; + // Just in case immutable objects were passed + NSData *remoteAddr = [inRemoteAddr copy]; + NSString *interface = [inInterface copy]; - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Check for problems with remoteAddr parameter + // Check for problems with remoteAddr parameter - NSData *address4 = nil; - NSData *address6 = nil; + NSData *address4 = nil; + NSData *address6 = nil; - if ([remoteAddr length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; + if ([remoteAddr length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes]; - if (sockaddr->sa_family == AF_INET) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in)) - { - address4 = remoteAddr; - } - } - else if (sockaddr->sa_family == AF_INET6) - { - if ([remoteAddr length] == sizeof(struct sockaddr_in6)) - { - address6 = remoteAddr; - } - } - } + if (sockaddr->sa_family == AF_INET) + { + if ([remoteAddr length] == sizeof(struct sockaddr_in)) + { + address4 = remoteAddr; + } + } + else if (sockaddr->sa_family == AF_INET6) + { + if ([remoteAddr length] == sizeof(struct sockaddr_in6)) + { + address6 = remoteAddr; + } + } + } - if ((address4 == nil) && (address6 == nil)) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; + if ((address4 == nil) && (address6 == nil)) + { + NSString *msg = @"A valid IPv4 or IPv6 address was not given"; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - if (isIPv4Disabled && (address4 != nil)) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; + if (isIPv4Disabled && (address4 != nil)) + { + NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - if (isIPv6Disabled && (address6 != nil)) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; + if (isIPv6Disabled && (address6 != nil)) + { + NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Run through standard pre-connect checks + // Run through standard pre-connect checks - if (![self preConnectWithInterface:interface error:&err]) - { - return_from_block; - } + if (![self preConnectWithInterface:interface error:&err]) + { + return_from_block; + } - // We've made it past all the checks. - // It's time to start the connection process. + // We've made it past all the checks. + // It's time to start the connection process. - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - return_from_block; - } + if (![self connectWithAddress4:address4 address6:address6 error:&err]) + { + return_from_block; + } - self->flags |= kSocketStarted; + self->flags |= kSocketStarted; - [self startConnectTimeout:timeout]; + [self startConnectTimeout:timeout]; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (result == NO) - { - if (errPtr) - *errPtr = err; - } + if (result == NO) + { + if (errPtr) + *errPtr = err; + } - return result; + return result; } - (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Check for problems with host parameter + // Check for problems with host parameter - if ([url.path length] == 0) - { - NSString *msg = @"Invalid unix domain socket url."; - err = [self badParamError:msg]; + if ([url.path length] == 0) + { + NSString *msg = @"Invalid unix domain socket url."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Run through standard pre-connect checks + // Run through standard pre-connect checks - if (![self preConnectWithUrl:url error:&err]) - { - return_from_block; - } + if (![self preConnectWithUrl:url error:&err]) + { + return_from_block; + } - // We've made it past all the checks. - // It's time to start the connection process. + // We've made it past all the checks. + // It's time to start the connection process. - self->flags |= kSocketStarted; + self->flags |= kSocketStarted; - // Start the normal connection process + // Start the normal connection process - NSError *connectError = nil; - if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError]) - { - [self closeWithError:connectError]; + NSError *connectError = nil; + if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError]) + { + [self closeWithError:connectError]; - return_from_block; - } + return_from_block; + } - [self startConnectTimeout:timeout]; + [self startConnectTimeout:timeout]; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (result == NO) - { - if (errPtr) - *errPtr = err; - } + if (result == NO) + { + if (errPtr) + *errPtr = err; + } - return result; + return result; } - (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6 { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(address4 || address6, @"Expected at least one valid address"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(address4 || address6, @"Expected at least one valid address"); - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring lookupDidSucceed, already disconnected"); - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } - // Check for problems + // Check for problems - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - if (isIPv4Disabled && (address6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; + if (isIPv4Disabled && (address6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address."; - [self closeWithError:[self otherError:msg]]; - return; - } + [self closeWithError:[self otherError:msg]]; + return; + } - if (isIPv6Disabled && (address4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; + if (isIPv6Disabled && (address4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address."; - [self closeWithError:[self otherError:msg]]; - return; - } + [self closeWithError:[self otherError:msg]]; + return; + } - // Start the normal connection process + // Start the normal connection process - NSError *err = nil; - if (![self connectWithAddress4:address4 address6:address6 error:&err]) - { - [self closeWithError:err]; - } + NSError *err = nil; + if (![self connectWithAddress4:address4 address6:address6 error:&err]) + { + [self closeWithError:err]; + } } /** @@ -2647,557 +2695,581 @@ - (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 addres * Since the DNS lookup executed synchronously on a global concurrent queue, * the original connection request may have already been cancelled or timed-out by the time this method is invoked. * The lookupIndex tells us whether the lookup is still valid or not. -**/ + **/ - (void)lookup:(int)aStateIndex didFail:(NSError *)error { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring lookup:didFail: - already disconnected"); + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring lookup:didFail: - already disconnected"); - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } - [self endConnectTimeout]; - [self closeWithError:error]; + [self endConnectTimeout]; + [self closeWithError:error]; } - (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr { - // Bind the socket to the desired interface (if needed) + // Bind the socket to the desired interface (if needed) - if (connectInterface) - { - LogVerbose(@"Binding socket..."); + if (connectInterface) + { + LogVerbose(@"Binding socket..."); - if ([[self class] portFromAddress:connectInterface] > 0) - { - // Since we're going to be binding to a specific port, - // we should turn on reuseaddr to allow us to override sockets in time_wait. + if ([[self class] portFromAddress:connectInterface] > 0) + { + // Since we're going to be binding to a specific port, + // we should turn on reuseaddr to allow us to override sockets in time_wait. - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); - } + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + } - const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; + const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes]; - int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]); - if (result != 0) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"]; + int result = bind(socketFD, + interfaceAddr, + (socklen_t)[connectInterface length]); + if (result != 0) + { + if (errPtr) + *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"]; - return NO; - } + return NO; } + } - return YES; + return YES; } - (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr { - int socketFD = socket(family, SOCK_STREAM, 0); + int socketFD = socket(family, SOCK_STREAM, 0); - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; - return socketFD; - } + return socketFD; + } - if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr]) - { - [self closeSocket:socketFD]; + if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr]) + { + [self closeSocket:socketFD]; - return SOCKET_NULL; - } + return SOCKET_NULL; + } - // Prevent SIGPIPE signals + // Prevent SIGPIPE signals - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + int nosigpipe = 1; + setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - return socketFD; + return socketFD; } - (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex { - // If there already is a socket connected, we close socketFD and return - if (self.isConnected) - { - [self closeSocket:socketFD]; - return; - } + // If there already is a socket connected, we close socketFD and return + if (self.isConnected) + { + [self closeSocket:socketFD]; + return; + } - // Start the connection process in a background queue + // Start the connection process in a background queue - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); + dispatch_async(globalConcurrentQueue, + ^{ #pragma clang diagnostic push #pragma clang diagnostic warning "-Wimplicit-retain-self" - int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]); - int err = errno; + int result = connect(socketFD, + (const struct sockaddr *)[address bytes], + (socklen_t)[address length]); + int err = errno; - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool { + dispatch_async(strongSelf->socketQueue, + ^{ @autoreleasepool { - if (strongSelf.isConnected) - { - [strongSelf closeSocket:socketFD]; - return_from_block; - } + if (strongSelf.isConnected) + { + [strongSelf closeSocket:socketFD]; + return_from_block; + } - if (result == 0) - { - [self closeUnusedSocket:socketFD]; + if (result == 0) + { + [self closeUnusedSocket:socketFD]; - [strongSelf didConnect:aStateIndex]; - } - else - { - [strongSelf closeSocket:socketFD]; - - // If there are no more sockets trying to connect, we inform the error to the delegate - if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL) - { - NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"]; - [strongSelf didNotConnect:aStateIndex error:error]; - } - } - }}); + [strongSelf didConnect:aStateIndex]; + } + else + { + [strongSelf closeSocket:socketFD]; + + // If there are no more sockets trying to connect, we inform the error to the delegate + if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL) + { + NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"]; + [strongSelf didNotConnect:aStateIndex error:error]; + } + } + }}); #pragma clang diagnostic pop - }); + }); - LogVerbose(@"Connecting..."); + LogVerbose(@"Connecting..."); } - (void)closeSocket:(int)socketFD { - if (socketFD != SOCKET_NULL && - (socketFD == socket6FD || socketFD == socket4FD)) - { - close(socketFD); + if (socketFD != SOCKET_NULL && + (socketFD == socket6FD || socketFD == socket4FD)) + { + close(socketFD); - if (socketFD == socket4FD) - { - LogVerbose(@"close(socket4FD)"); - socket4FD = SOCKET_NULL; - } - else if (socketFD == socket6FD) - { - LogVerbose(@"close(socket6FD)"); - socket6FD = SOCKET_NULL; - } + if (socketFD == socket4FD) + { + LogVerbose(@"close(socket4FD)"); + socket4FD = SOCKET_NULL; + } + else if (socketFD == socket6FD) + { + LogVerbose(@"close(socket6FD)"); + socket6FD = SOCKET_NULL; } + } } - (void)closeUnusedSocket:(int)usedSocketFD { - if (usedSocketFD != socket4FD) - { - [self closeSocket:socket4FD]; - } - else if (usedSocketFD != socket6FD) - { - [self closeSocket:socket6FD]; - } + if (usedSocketFD != socket4FD) + { + [self closeSocket:socket4FD]; + } + else if (usedSocketFD != socket6FD) + { + [self closeSocket:socket6FD]; + } } - (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]); - LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]); + LogVerbose(@"IPv4: %@:%hu", + [[self class] hostFromAddress:address4], + [[self class] portFromAddress:address4]); + LogVerbose(@"IPv6: %@:%hu", + [[self class] hostFromAddress:address6], + [[self class] portFromAddress:address6]); - // Determine socket type + // Determine socket type - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; + BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - // Create and bind the sockets + // Create and bind the sockets - if (address4) - { - LogVerbose(@"Creating IPv4 socket"); + if (address4) + { + LogVerbose(@"Creating IPv4 socket"); - socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr]; - } + socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr]; + } - if (address6) - { - LogVerbose(@"Creating IPv6 socket"); + if (address6) + { + LogVerbose(@"Creating IPv6 socket"); - socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr]; - } + socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr]; + } - if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) - { - return NO; - } + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + return NO; + } - int socketFD, alternateSocketFD; - NSData *address, *alternateAddress; + int socketFD, alternateSocketFD; + NSData *address, *alternateAddress; - if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL) - { - socketFD = socket6FD; - alternateSocketFD = socket4FD; - address = address6; - alternateAddress = address4; - } - else - { - socketFD = socket4FD; - alternateSocketFD = socket6FD; - address = address4; - alternateAddress = address6; - } + if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL) + { + socketFD = socket6FD; + alternateSocketFD = socket4FD; + address = address6; + alternateAddress = address4; + } + else + { + socketFD = socket4FD; + alternateSocketFD = socket6FD; + address = address4; + alternateAddress = address6; + } - int aStateIndex = stateIndex; + int aStateIndex = stateIndex; - [self connectSocket:socketFD address:address stateIndex:aStateIndex]; + [self connectSocket:socketFD address:address stateIndex:aStateIndex]; - if (alternateAddress) - { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{ - [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex]; - }); - } + if (alternateAddress) + { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), + socketQueue, + ^{ + [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex]; + }); + } - return YES; + return YES; } - (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - // Create the socket + // Create the socket - int socketFD; + int socketFD; - LogVerbose(@"Creating unix domain socket"); + LogVerbose(@"Creating unix domain socket"); - socketUN = socket(AF_UNIX, SOCK_STREAM, 0); + socketUN = socket(AF_UNIX, SOCK_STREAM, 0); - socketFD = socketUN; + socketFD = socketUN; - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"]; - return NO; - } + return NO; + } - // Bind the socket to the desired interface (if needed) + // Bind the socket to the desired interface (if needed) - LogVerbose(@"Binding socket..."); + LogVerbose(@"Binding socket..."); - int reuseOn = 1; - setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); + int reuseOn = 1; + setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); -// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; -// -// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); -// if (result != 0) -// { -// if (errPtr) -// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; -// -// return NO; -// } + // const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes]; + // + // int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]); + // if (result != 0) + // { + // if (errPtr) + // *errPtr = [self errnoErrorWithReason:@"Error in bind() function"]; + // + // return NO; + // } - // Prevent SIGPIPE signals + // Prevent SIGPIPE signals - int nosigpipe = 1; - setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); + int nosigpipe = 1; + setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - // Start the connection process in a background queue + // Start the connection process in a background queue - int aStateIndex = stateIndex; + int aStateIndex = stateIndex; - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); + dispatch_async(globalConcurrentQueue, + ^{ - const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; - int result = connect(socketFD, addr, addr->sa_len); - if (result == 0) - { - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + const struct sockaddr *addr = (const struct sockaddr *)[address bytes]; + int result = connect(socketFD, addr, addr->sa_len); + if (result == 0) + { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self didConnect:aStateIndex]; - }}); - } - else - { - // TODO: Bad file descriptor - perror("connect"); - NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"]; + [self didConnect:aStateIndex]; + }}); + } + else + { + // TODO: Bad file descriptor + perror("connect"); + NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"]; - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self didNotConnect:aStateIndex error:error]; - }}); - } - }); + [self didNotConnect:aStateIndex error:error]; + }}); + } + }); - LogVerbose(@"Connecting..."); + LogVerbose(@"Connecting..."); - return YES; + return YES; } - (void)didConnect:(int)aStateIndex { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didConnect, already disconnected"); + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring didConnect, already disconnected"); - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } - flags |= kConnected; + flags |= kConnected; - [self endConnectTimeout]; + [self endConnectTimeout]; - #if TARGET_OS_IPHONE - // The endConnectTimeout method executed above incremented the stateIndex. - aStateIndex = stateIndex; - #endif +#if TARGET_OS_IPHONE + // The endConnectTimeout method executed above incremented the stateIndex. + aStateIndex = stateIndex; +#endif - // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) - // - // Note: - // There may be configuration options that must be set by the delegate before opening the streams. - // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. - // - // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. - // This gives the delegate time to properly configure the streams if needed. + // Setup read/write streams (as workaround for specific shortcomings in the iOS platform) + // + // Note: + // There may be configuration options that must be set by the delegate before opening the streams. + // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream. + // + // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed. + // This gives the delegate time to properly configure the streams if needed. - dispatch_block_t SetupStreamsPart1 = ^{ - #if TARGET_OS_IPHONE + dispatch_block_t SetupStreamsPart1 = ^{ +#if TARGET_OS_IPHONE - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } + if (![self createReadAndWriteStream]) + { + [self closeWithError:[self otherError:@"Error creating CFStreams"]]; + return; + } - if (![self registerForStreamCallbacksIncludingReadWrite:NO]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } + if (![self registerForStreamCallbacksIncludingReadWrite:NO]) + { + [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; + return; + } - #endif - }; - dispatch_block_t SetupStreamsPart2 = ^{ - #if TARGET_OS_IPHONE +#endif + }; + dispatch_block_t SetupStreamsPart2 = ^{ +#if TARGET_OS_IPHONE - if (aStateIndex != self->stateIndex) - { - // The socket has been disconnected. - return; - } + if (aStateIndex != self->stateIndex) + { + // The socket has been disconnected. + return; + } - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } + if (![self addStreamsToRunLoop]) + { + [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; + return; + } - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error creating CFStreams"]]; - return; - } + if (![self openStreams]) + { + [self closeWithError:[self otherError:@"Error creating CFStreams"]]; + return; + } - #endif - }; +#endif + }; - // Notify delegate + // Notify delegate - NSString *host = [self connectedHost]; - uint16_t port = [self connectedPort]; - NSURL *url = [self connectedUrl]; + NSString *host = [self connectedHost]; + uint16_t port = [self connectedPort]; + NSURL *url = [self connectedUrl]; - __strong id theDelegate = delegate; + __strong id theDelegate = delegate; - if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) - { - SetupStreamsPart1(); + if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) + { + SetupStreamsPart1(); - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate socket:self didConnectToHost:host port:port]; + [theDelegate socket:self didConnectToHost:host port:port]; - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - SetupStreamsPart2(); - }}); - }}); - } - else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) - { - SetupStreamsPart1(); + SetupStreamsPart2(); + }}); + }}); + } + else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)]) + { + SetupStreamsPart1(); - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate socket:self didConnectToUrl:url]; + [theDelegate socket:self didConnectToUrl:url]; - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - SetupStreamsPart2(); - }}); - }}); - } - else - { - SetupStreamsPart1(); - SetupStreamsPart2(); - } + SetupStreamsPart2(); + }}); + }}); + } + else + { + SetupStreamsPart1(); + SetupStreamsPart2(); + } - // Get the connected socket + // Get the connected socket - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - // Enable non-blocking IO on the socket + // Enable non-blocking IO on the socket - int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (result == -1) - { - NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; - [self closeWithError:[self otherError:errMsg]]; + int result = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (result == -1) + { + NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)"; + [self closeWithError:[self otherError:errMsg]]; - return; - } + return; + } - // Setup our read/write sources + // Setup our read/write sources - [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; + [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD]; - // Dequeue any pending read/write requests + // Dequeue any pending read/write requests - [self maybeDequeueRead]; - [self maybeDequeueWrite]; + [self maybeDequeueRead]; + [self maybeDequeueWrite]; } - (void)didNotConnect:(int)aStateIndex error:(NSError *)error { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring didNotConnect, already disconnected"); + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring didNotConnect, already disconnected"); - // The connect operation has been cancelled. - // That is, socket was disconnected, or connection has already timed out. - return; - } + // The connect operation has been cancelled. + // That is, socket was disconnected, or connection has already timed out. + return; + } - [self closeWithError:error]; + [self closeWithError:error]; } - (void)startConnectTimeout:(NSTimeInterval)timeout { - if (timeout >= 0.0) - { - connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + if (timeout >= 0.0) + { + connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + socketQueue); - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - [strongSelf doConnectTimeout]; + [strongSelf doConnectTimeout]; - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theConnectTimer = connectTimer; - dispatch_source_set_cancel_handler(connectTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theConnectTimer = connectTimer; + dispatch_source_set_cancel_handler(connectTimer, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - LogVerbose(@"dispatch_release(connectTimer)"); - dispatch_release(theConnectTimer); + LogVerbose(@"dispatch_release(connectTimer)"); + dispatch_release(theConnectTimer); - #pragma clang diagnostic pop - }); - #endif +#pragma clang diagnostic pop + }); +#endif - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeout * NSEC_PER_SEC)); + dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(connectTimer); - } + dispatch_resume(connectTimer); + } } - (void)endConnectTimeout { - LogTrace(); + LogTrace(); - if (connectTimer) - { - dispatch_source_cancel(connectTimer); - connectTimer = NULL; - } + if (connectTimer) + { + dispatch_source_cancel(connectTimer); + connectTimer = NULL; + } - // Increment stateIndex. - // This will prevent us from processing results from any related background asynchronous operations. - // - // Note: This should be called from close method even if connectTimer is NULL. - // This is because one might disconnect a socket prior to a successful connection which had no timeout. + // Increment stateIndex. + // This will prevent us from processing results from any related background asynchronous operations. + // + // Note: This should be called from close method even if connectTimer is NULL. + // This is because one might disconnect a socket prior to a successful connection which had no timeout. - stateIndex++; + stateIndex++; - if (connectInterface4) - { - connectInterface4 = nil; - } - if (connectInterface6) - { - connectInterface6 = nil; - } + if (connectInterface4) + { + connectInterface4 = nil; + } + if (connectInterface6) + { + connectInterface6 = nil; + } } - (void)doConnectTimeout { - LogTrace(); + LogTrace(); - [self endConnectTimeout]; - [self closeWithError:[self connectTimeoutError]]; + [self endConnectTimeout]; + [self closeWithError:[self connectTimeoutError]]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3206,394 +3278,407 @@ - (void)doConnectTimeout - (void)closeWithError:(NSError *)error { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - [self endConnectTimeout]; - - if (currentRead != nil) [self endCurrentRead]; - if (currentWrite != nil) [self endCurrentWrite]; - - [readQueue removeAllObjects]; - [writeQueue removeAllObjects]; - - [preBuffer reset]; - - #if TARGET_OS_IPHONE - { - if (readStream || writeStream) - { - [self removeStreamsFromRunLoop]; - - if (readStream) - { - CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } - } - } - #endif - - [sslPreBuffer reset]; - sslErrCode = lastSSLHandshakeError = noErr; - - if (sslContext) - { - // Getting a linker error here about the SSLx() functions? - // You need to add the Security Framework to your application. - - SSLClose(sslContext); - - #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - CFRelease(sslContext); - #else - SSLDisposeContext(sslContext); - #endif - - sslContext = NULL; - } - - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - - if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource) - { - LogVerbose(@"manually closing close"); - - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"close(socket4FD)"); - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"close(socket6FD)"); - close(socket6FD); - socket6FD = SOCKET_NULL; - } - - if (socketUN != SOCKET_NULL) - { - LogVerbose(@"close(socketUN)"); - close(socketUN); - socketUN = SOCKET_NULL; - unlink(socketUrl.path.fileSystemRepresentation); - socketUrl = nil; - } - } - else - { - if (accept4Source) - { - LogVerbose(@"dispatch_source_cancel(accept4Source)"); - dispatch_source_cancel(accept4Source); - - // We never suspend accept4Source - - accept4Source = NULL; - } - - if (accept6Source) - { - LogVerbose(@"dispatch_source_cancel(accept6Source)"); - dispatch_source_cancel(accept6Source); - - // We never suspend accept6Source - - accept6Source = NULL; - } - - if (acceptUNSource) - { - LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); - dispatch_source_cancel(acceptUNSource); - - // We never suspend acceptUNSource - - acceptUNSource = NULL; - } - - if (readSource) - { - LogVerbose(@"dispatch_source_cancel(readSource)"); - dispatch_source_cancel(readSource); - - [self resumeReadSource]; - - readSource = NULL; - } - - if (writeSource) - { - LogVerbose(@"dispatch_source_cancel(writeSource)"); - dispatch_source_cancel(writeSource); - - [self resumeWriteSource]; - - writeSource = NULL; - } - - // The sockets will be closed by the cancel handlers of the corresponding source - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - socketUN = SOCKET_NULL; - } - - // If the client has passed the connect/accept method, then the connection has at least begun. - // Notify delegate that it is now ending. - BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; - BOOL isDeallocating = (flags & kDealloc) ? YES : NO; - - // Clear stored socket info and all flags (config remains as is) - socketFDBytesAvailable = 0; - flags = 0; - sslWriteCachedLength = 0; - - if (shouldCallDelegate) - { - __strong id theDelegate = delegate; - __strong id theSelf = isDeallocating ? nil : self; - - if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidDisconnect:theSelf withError:error]; - }}); - } - } -} - -- (void)disconnect -{ - dispatch_block_t block = ^{ @autoreleasepool { + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (self->flags & kSocketStarted) - { - [self closeWithError:nil]; - } - }}; + [self endConnectTimeout]; - // Synchronous disconnection, as documented in the header file + if (currentRead != nil) [self endCurrentRead]; + if (currentWrite != nil) [self endCurrentWrite]; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); -} + [readQueue removeAllObjects]; + [writeQueue removeAllObjects]; -- (void)disconnectAfterReading -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { + [preBuffer reset]; - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterReads); - [self maybeClose]; - } - }}); -} +#if TARGET_OS_IPHONE + { + if (readStream || writeStream) + { + [self removeStreamsFromRunLoop]; + + if (readStream) + { + CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream); + CFRelease(readStream); + readStream = NULL; + } + if (writeStream) + { + CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + writeStream = NULL; + } + } + } +#endif -- (void)disconnectAfterWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { + [sslPreBuffer reset]; + sslErrCode = lastSSLHandshakeError = noErr; - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} + if (sslContext) + { + // Getting a linker error here about the SSLx() functions? + // You need to add the Security Framework to your application. -- (void)disconnectAfterReadingAndWriting -{ - dispatch_async(socketQueue, ^{ @autoreleasepool { + SSLClose(sslContext); - if (self->flags & kSocketStarted) - { - self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); - [self maybeClose]; - } - }}); -} +#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) + CFRelease(sslContext); +#else + SSLDisposeContext(sslContext); +#endif -/** - * Closes the socket if possible. - * That is, if all writes have completed, and we're set to disconnect after writing, - * or if all reads have completed, and we're set to disconnect after reading. -**/ -- (void)maybeClose -{ - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - BOOL shouldClose = NO; - - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - else - { - shouldClose = YES; - } - } - } - else if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - shouldClose = YES; - } - } - - if (shouldClose) - { - [self closeWithError:nil]; - } -} + sslContext = NULL; + } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Errors -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. -- (NSError *)badConfigError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource) + { + LogVerbose(@"manually closing close"); - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; -} + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"close(socket4FD)"); + close(socket4FD); + socket4FD = SOCKET_NULL; + } -- (NSError *)badParamError:(NSString *)errMsg -{ - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + if (socket6FD != SOCKET_NULL) + { + LogVerbose(@"close(socket6FD)"); + close(socket6FD); + socket6FD = SOCKET_NULL; + } - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; -} + if (socketUN != SOCKET_NULL) + { + LogVerbose(@"close(socketUN)"); + close(socketUN); + socketUN = SOCKET_NULL; + unlink(socketUrl.path.fileSystemRepresentation); + socketUrl = nil; + } + } + else + { + if (accept4Source) + { + LogVerbose(@"dispatch_source_cancel(accept4Source)"); + dispatch_source_cancel(accept4Source); + + // We never suspend accept4Source + + accept4Source = NULL; + } + + if (accept6Source) + { + LogVerbose(@"dispatch_source_cancel(accept6Source)"); + dispatch_source_cancel(accept6Source); + + // We never suspend accept6Source + + accept6Source = NULL; + } + + if (acceptUNSource) + { + LogVerbose(@"dispatch_source_cancel(acceptUNSource)"); + dispatch_source_cancel(acceptUNSource); + + // We never suspend acceptUNSource + + acceptUNSource = NULL; + } + + if (readSource) + { + LogVerbose(@"dispatch_source_cancel(readSource)"); + dispatch_source_cancel(readSource); + + [self resumeReadSource]; + + readSource = NULL; + } + + if (writeSource) + { + LogVerbose(@"dispatch_source_cancel(writeSource)"); + dispatch_source_cancel(writeSource); + + [self resumeWriteSource]; + + writeSource = NULL; + } + + // The sockets will be closed by the cancel handlers of the corresponding source + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + socketUN = SOCKET_NULL; + } + + // If the client has passed the connect/accept method, then the connection has at least begun. + // Notify delegate that it is now ending. + BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO; + BOOL isDeallocating = (flags & kDealloc) ? YES : NO; + + // Clear stored socket info and all flags (config remains as is) + socketFDBytesAvailable = 0; + flags = 0; + sslWriteCachedLength = 0; + + if (shouldCallDelegate) + { + __strong id theDelegate = delegate; + __strong id theSelf = isDeallocating ? nil : self; + + if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidDisconnect:theSelf withError:error]; + }}); + } + } +} + +- (void)disconnect +{ + dispatch_block_t block = ^{ @autoreleasepool { + + if (self->flags & kSocketStarted) + { + [self closeWithError:nil]; + } + }}; + + // Synchronous disconnection, as documented in the header file + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); +} + +- (void)disconnectAfterReading +{ + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if (self->flags & kSocketStarted) + { + self->flags |= (kForbidReadsWrites | kDisconnectAfterReads); + [self maybeClose]; + } + }}); +} + +- (void)disconnectAfterWriting +{ + dispatch_async(socketQueue, ^{ @autoreleasepool { + + if (self->flags & kSocketStarted) + { + self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites); + [self maybeClose]; + } + }}); +} + +- (void)disconnectAfterReadingAndWriting +{ + dispatch_async(socketQueue, + ^{ @autoreleasepool { + + if (self->flags & kSocketStarted) + { + self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); + [self maybeClose]; + } + }}); +} + +/** + * Closes the socket if possible. + * That is, if all writes have completed, and we're set to disconnect after writing, + * or if all reads have completed, and we're set to disconnect after reading. + **/ +- (void)maybeClose +{ + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + BOOL shouldClose = NO; + + if (flags & kDisconnectAfterReads) + { + if (([readQueue count] == 0) && (currentRead == nil)) + { + if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + shouldClose = YES; + } + } + else + { + shouldClose = YES; + } + } + } + else if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + shouldClose = YES; + } + } + + if (shouldClose) + { + [self closeWithError:nil]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Errors +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSError *)badConfigError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo]; +} + +- (NSError *)badParamError:(NSString *)errMsg +{ + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo]; +} + (NSError *)gaiError:(int)gai_error { - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; + return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; } - (NSError *)errorWithErrno:(int)err reason:(NSString *)reason { - NSString *errMsg = [NSString stringWithUTF8String:strerror(err)]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg, - NSLocalizedFailureReasonErrorKey : reason}; + NSString *errMsg = [NSString stringWithUTF8String:strerror(err)]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo]; + return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo]; } - (NSError *)errnoError { - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; } - (NSError *)sslError:(OSStatus)ssl_error { - NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; - NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg}; + NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h"; + NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg}; - return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; + return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo]; } - (NSError *)connectTimeoutError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Attempt to connect to host timed out", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError", + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Attempt to connect to host timed out", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo]; } /** * Returns a standard AsyncSocket maxed out error. -**/ + **/ - (NSError *)readMaxedOutError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation reached set maximum length", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError", + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Read operation reached set maximum length", + nil); - NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info]; } /** * Returns a standard AsyncSocket write timeout error. -**/ + **/ - (NSError *)readTimeoutError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Read operation timed out", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError", + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Read operation timed out", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo]; } /** * Returns a standard AsyncSocket write timeout error. -**/ + **/ - (NSError *)writeTimeoutError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Write operation timed out", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError", + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Write operation timed out", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo]; } - (NSError *)connectionClosedError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", - @"GCDAsyncSocket", [NSBundle mainBundle], - @"Socket closed by remote peer", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError", + @"GCDAsyncSocket", + [NSBundle mainBundle], + @"Socket closed by remote peer", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo]; } - (NSError *)otherError:(NSString *)errMsg { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3602,465 +3687,473 @@ - (NSError *)otherError:(NSString *)errMsg - (BOOL)isDisconnected { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->flags & kSocketStarted) ? NO : YES; - }; + dispatch_block_t block = ^{ + result = (self->flags & kSocketStarted) ? NO : YES; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isConnected { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->flags & kConnected) ? YES : NO; - }; + dispatch_block_t block = ^{ + result = (self->flags & kConnected) ? YES : NO; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (NSString *)connectedHost { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self connectedHostFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedHostFromSocket6:socket6FD]; - return nil; - } - else - { - __block NSString *result = nil; + return nil; + } + else + { + __block NSString *result = nil; - dispatch_sync(socketQueue, ^{ @autoreleasepool { + dispatch_sync(socketQueue, ^{ @autoreleasepool { - if (self->socket4FD != SOCKET_NULL) - result = [self connectedHostFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self connectedHostFromSocket6:self->socket6FD]; - }}); + if (self->socket4FD != SOCKET_NULL) + result = [self connectedHostFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self connectedHostFromSocket6:self->socket6FD]; + }}); - return result; - } + return result; + } } - (uint16_t)connectedPort { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self connectedPortFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedPortFromSocket6:socket6FD]; - return 0; - } - else - { - __block uint16_t result = 0; + return 0; + } + else + { + __block uint16_t result = 0; - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool + dispatch_sync(socketQueue, ^{ + // No need for autorelease pool - if (self->socket4FD != SOCKET_NULL) - result = [self connectedPortFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self connectedPortFromSocket6:self->socket6FD]; - }); + if (self->socket4FD != SOCKET_NULL) + result = [self connectedPortFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self connectedPortFromSocket6:self->socket6FD]; + }); - return result; - } + return result; + } } - (NSURL *)connectedUrl { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socketUN != SOCKET_NULL) - return [self connectedUrlFromSocketUN:socketUN]; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socketUN != SOCKET_NULL) + return [self connectedUrlFromSocketUN:socketUN]; - return nil; - } - else - { - __block NSURL *result = nil; + return nil; + } + else + { + __block NSURL *result = nil; - dispatch_sync(socketQueue, ^{ @autoreleasepool { + dispatch_sync(socketQueue, ^{ @autoreleasepool { - if (self->socketUN != SOCKET_NULL) - result = [self connectedUrlFromSocketUN:self->socketUN]; - }}); + if (self->socketUN != SOCKET_NULL) + result = [self connectedUrlFromSocketUN:self->socketUN]; + }}); - return result; - } + return result; + } } - (NSString *)localHost { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self localHostFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self localHostFromSocket6:socket6FD]; - return nil; - } - else - { - __block NSString *result = nil; + return nil; + } + else + { + __block NSString *result = nil; - dispatch_sync(socketQueue, ^{ @autoreleasepool { + dispatch_sync(socketQueue, ^{ @autoreleasepool { - if (self->socket4FD != SOCKET_NULL) - result = [self localHostFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self localHostFromSocket6:self->socket6FD]; - }}); + if (self->socket4FD != SOCKET_NULL) + result = [self localHostFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self localHostFromSocket6:self->socket6FD]; + }}); - return result; - } + return result; + } } - (uint16_t)localPort { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (socket4FD != SOCKET_NULL) + return [self localPortFromSocket4:socket4FD]; + if (socket6FD != SOCKET_NULL) + return [self localPortFromSocket6:socket6FD]; - return 0; - } - else - { - __block uint16_t result = 0; + return 0; + } + else + { + __block uint16_t result = 0; - dispatch_sync(socketQueue, ^{ - // No need for autorelease pool + dispatch_sync(socketQueue, ^{ + // No need for autorelease pool - if (self->socket4FD != SOCKET_NULL) - result = [self localPortFromSocket4:self->socket4FD]; - else if (self->socket6FD != SOCKET_NULL) - result = [self localPortFromSocket6:self->socket6FD]; - }); + if (self->socket4FD != SOCKET_NULL) + result = [self localPortFromSocket4:self->socket4FD]; + else if (self->socket6FD != SOCKET_NULL) + result = [self localPortFromSocket6:self->socket6FD]; + }); - return result; - } + return result; + } } - (NSString *)connectedHost4 { - if (socket4FD != SOCKET_NULL) - return [self connectedHostFromSocket4:socket4FD]; + if (socket4FD != SOCKET_NULL) + return [self connectedHostFromSocket4:socket4FD]; - return nil; + return nil; } - (NSString *)connectedHost6 { - if (socket6FD != SOCKET_NULL) - return [self connectedHostFromSocket6:socket6FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedHostFromSocket6:socket6FD]; - return nil; + return nil; } - (uint16_t)connectedPort4 { - if (socket4FD != SOCKET_NULL) - return [self connectedPortFromSocket4:socket4FD]; + if (socket4FD != SOCKET_NULL) + return [self connectedPortFromSocket4:socket4FD]; - return 0; + return 0; } - (uint16_t)connectedPort6 { - if (socket6FD != SOCKET_NULL) - return [self connectedPortFromSocket6:socket6FD]; + if (socket6FD != SOCKET_NULL) + return [self connectedPortFromSocket6:socket6FD]; - return 0; + return 0; } - (NSString *)localHost4 { - if (socket4FD != SOCKET_NULL) - return [self localHostFromSocket4:socket4FD]; + if (socket4FD != SOCKET_NULL) + return [self localHostFromSocket4:socket4FD]; - return nil; + return nil; } - (NSString *)localHost6 { - if (socket6FD != SOCKET_NULL) - return [self localHostFromSocket6:socket6FD]; + if (socket6FD != SOCKET_NULL) + return [self localHostFromSocket6:socket6FD]; - return nil; + return nil; } - (uint16_t)localPort4 { - if (socket4FD != SOCKET_NULL) - return [self localPortFromSocket4:socket4FD]; + if (socket4FD != SOCKET_NULL) + return [self localPortFromSocket4:socket4FD]; - return 0; + return 0; } - (uint16_t)localPort6 { - if (socket6FD != SOCKET_NULL) - return [self localPortFromSocket6:socket6FD]; + if (socket6FD != SOCKET_NULL) + return [self localPortFromSocket6:socket6FD]; - return 0; + return 0; } - (NSString *)connectedHostFromSocket4:(int)socketFD { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; + if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr4:&sockaddr4]; } - (NSString *)connectedHostFromSocket6:(int)socketFD { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; + if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr6:&sockaddr6]; } - (uint16_t)connectedPortFromSocket4:(int)socketFD { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); - if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; + if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr4:&sockaddr4]; } - (uint16_t)connectedPortFromSocket6:(int)socketFD { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); - if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; + if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr6:&sockaddr6]; } - (NSURL *)connectedUrlFromSocketUN:(int)socketFD { - struct sockaddr_un sockaddr; - socklen_t sockaddrlen = sizeof(sockaddr); + struct sockaddr_un sockaddr; + socklen_t sockaddrlen = sizeof(sockaddr); - if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) - { - return 0; - } - return [[self class] urlFromSockaddrUN:&sockaddr]; + if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0) + { + return 0; + } + return [[self class] urlFromSockaddrUN:&sockaddr]; } - (NSString *)localHostFromSocket4:(int)socketFD { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr4:&sockaddr4]; + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr4:&sockaddr4]; } - (NSString *)localHostFromSocket6:(int)socketFD { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return nil; - } - return [[self class] hostFromSockaddr6:&sockaddr6]; + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return nil; + } + return [[self class] hostFromSockaddr6:&sockaddr6]; } - (uint16_t)localPortFromSocket4:(int)socketFD { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr4:&sockaddr4]; + if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr4:&sockaddr4]; } - (uint16_t)localPortFromSocket6:(int)socketFD { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) - { - return 0; - } - return [[self class] portFromSockaddr6:&sockaddr6]; + if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) + { + return 0; + } + return [[self class] portFromSockaddr6:&sockaddr6]; } - (NSData *)connectedAddress { - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } + __block NSData *result = nil; - if (self->socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + dispatch_block_t block = ^{ + if (self->socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(self->socket4FD, + (struct sockaddr *)&sockaddr4, + &sockaddr4len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; + } + } - if (getpeername(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; + if (self->socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(self->socket6FD, + (struct sockaddr *)&sockaddr6, + &sockaddr6len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; + } + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (NSData *)localAddress { - __block NSData *result = nil; - - dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); + __block NSData *result = nil; - if (getsockname(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; - } - } - - if (self->socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); + dispatch_block_t block = ^{ + if (self->socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(self->socket4FD, + (struct sockaddr *)&sockaddr4, + &sockaddr4len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len]; + } + } - if (getsockname(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; - } - } - }; + if (self->socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(self->socket6FD, + (struct sockaddr *)&sockaddr6, + &sockaddr6len) == 0) + { + result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len]; + } + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isIPv4 { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket4FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (socket4FD != SOCKET_NULL); + } + else + { + __block BOOL result = NO; - dispatch_sync(socketQueue, ^{ - result = (self->socket4FD != SOCKET_NULL); - }); + dispatch_sync(socketQueue, ^{ + result = (self->socket4FD != SOCKET_NULL); + }); - return result; - } + return result; + } } - (BOOL)isIPv6 { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (socket6FD != SOCKET_NULL); - } - else - { - __block BOOL result = NO; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (socket6FD != SOCKET_NULL); + } + else + { + __block BOOL result = NO; - dispatch_sync(socketQueue, ^{ - result = (self->socket6FD != SOCKET_NULL); - }); + dispatch_sync(socketQueue, ^{ + result = (self->socket6FD != SOCKET_NULL); + }); - return result; - } + return result; + } } - (BOOL)isSecure { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return (flags & kSocketSecure) ? YES : NO; - } - else - { - __block BOOL result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return (flags & kSocketSecure) ? YES : NO; + } + else + { + __block BOOL result; - dispatch_sync(socketQueue, ^{ - result = (self->flags & kSocketSecure) ? YES : NO; - }); + dispatch_sync(socketQueue, ^{ + result = (self->flags & kSocketSecure) ? YES : NO; + }); - return result; - } + return result; + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4075,367 +4168,383 @@ - (BOOL)isSecure * If a non-zero port parameter is provided, any port number in the interface description is ignored. * * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object. -**/ + **/ - (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr address6:(NSMutableData **)interfaceAddr6Ptr fromDescription:(NSString *)interfaceDescription port:(uint16_t)port { - NSMutableData *addr4 = nil; - NSMutableData *addr6 = nil; - - NSString *interface = nil; - - NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; - if ([components count] > 0) - { - NSString *temp = [components objectAtIndex:0]; - if ([temp length] > 0) - { - interface = temp; - } - } - if ([components count] > 1 && port == 0) - { - NSString *temp = [components objectAtIndex:1]; - long portL = strtol([temp UTF8String], NULL, 10); - - if (portL > 0 && portL <= UINT16_MAX) - { - port = (uint16_t)portL; - } - } - - if (interface == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interface UTF8String]; - - struct ifaddrs *addrs; - const struct ifaddrs *cursor; - - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 - - struct sockaddr_in nativeAddr4; - memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; - - const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - nativeAddr4.sin_port = htons(port); - - addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 + NSMutableData *addr4 = nil; + NSMutableData *addr6 = nil; + + NSString *interface = nil; + + NSArray *components = [interfaceDescription componentsSeparatedByString:@":"]; + if ([components count] > 0) + { + NSString *temp = [components objectAtIndex:0]; + if ([temp length] > 0) + { + interface = temp; + } + } + if ([components count] > 1 && port == 0) + { + NSString *temp = [components objectAtIndex:1]; + long portL = strtol([temp UTF8String], NULL, 10); + + if (portL > 0 && portL <= UINT16_MAX) + { + port = (uint16_t)portL; + } + } + + if (interface == nil) + { + // ANY address - struct sockaddr_in6 nativeAddr6; - memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - nativeAddr6.sin6_port = htons(port); + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_any; - const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip)); + addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) + { + // LOOPBACK address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else + { + const char *iface = [interface UTF8String]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) + { + // IPv4 + + struct sockaddr_in nativeAddr4; + memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4)); + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match + + nativeAddr4.sin_port = htons(port); + + addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + else + { + char ip[INET_ADDRSTRLEN]; + + const char *conversion = inet_ntop(AF_INET, + &nativeAddr4.sin_addr, + ip, + sizeof(ip)); - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + nativeAddr4.sin_port = htons(port); + + addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + } + } + else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) + { + // IPv6 + + struct sockaddr_in6 nativeAddr6; + memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6)); + + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match - nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_port = htons(port); - addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } + addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + char ip[INET6_ADDRSTRLEN]; - cursor = cursor->ifa_next; - } + const char *conversion = inet_ntop(AF_INET6, + &nativeAddr6.sin6_addr, + ip, + sizeof(ip)); - freeifaddrs(addrs); - } - } + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match + + nativeAddr6.sin6_port = htons(port); + + addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs(addrs); + } + } - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; + if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; + if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; } - (NSData *)getInterfaceAddressFromUrl:(NSURL *)url { - NSString *path = url.path; - if (path.length == 0) { - return nil; - } + NSString *path = url.path; + if (path.length == 0) { + return nil; + } - struct sockaddr_un nativeAddr; - nativeAddr.sun_family = AF_UNIX; - strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path)); - nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr); - NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; + struct sockaddr_un nativeAddr; + nativeAddr.sun_family = AF_UNIX; + strlcpy(nativeAddr.sun_path, + path.fileSystemRepresentation, + sizeof(nativeAddr.sun_path)); + nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr); + NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)]; - return interface; + return interface; } - (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD { - readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue); - writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue); + readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + socketFD, + 0, + socketQueue); + writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, + socketFD, + 0, + socketQueue); - // Setup event handlers + // Setup event handlers - __weak GCDAsyncSocket *weakSelf = self; + __weak GCDAsyncSocket *weakSelf = self; - dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_event_handler(readSource, + ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - LogVerbose(@"readEventBlock"); + LogVerbose(@"readEventBlock"); - strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); - LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable); + strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource); + LogVerbose(@"socketFDBytesAvailable: %lu", + strongSelf->socketFDBytesAvailable); - if (strongSelf->socketFDBytesAvailable > 0) - [strongSelf doReadData]; - else - [strongSelf doReadEOF]; + if (strongSelf->socketFDBytesAvailable > 0) + [strongSelf doReadData]; + else + [strongSelf doReadEOF]; - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - LogVerbose(@"writeEventBlock"); + LogVerbose(@"writeEventBlock"); - strongSelf->flags |= kSocketCanAcceptBytes; - [strongSelf doWriteData]; + strongSelf->flags |= kSocketCanAcceptBytes; + [strongSelf doWriteData]; - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); - // Setup cancel handlers + // Setup cancel handlers - __block int socketFDRefCount = 2; + __block int socketFDRefCount = 2; - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadSource = readSource; - dispatch_source_t theWriteSource = writeSource; - #endif +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theReadSource = readSource; + dispatch_source_t theWriteSource = writeSource; +#endif - dispatch_source_set_cancel_handler(readSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_cancel_handler(readSource, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - LogVerbose(@"readCancelBlock"); + LogVerbose(@"readCancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(readSource)"); - dispatch_release(theReadSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(readSource)"); + dispatch_release(theReadSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socketFD)"); + close(socketFD); + } - #pragma clang diagnostic pop - }); +#pragma clang diagnostic pop + }); - dispatch_source_set_cancel_handler(writeSource, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_source_set_cancel_handler(writeSource, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - LogVerbose(@"writeCancelBlock"); + LogVerbose(@"writeCancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(writeSource)"); - dispatch_release(theWriteSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(writeSource)"); + dispatch_release(theWriteSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socketFD)"); - close(socketFD); - } + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socketFD)"); + close(socketFD); + } - #pragma clang diagnostic pop - }); +#pragma clang diagnostic pop + }); - // We will not be able to read until data arrives. - // But we should be able to write immediately. + // We will not be able to read until data arrives. + // But we should be able to write immediately. - socketFDBytesAvailable = 0; - flags &= ~kReadSourceSuspended; + socketFDBytesAvailable = 0; + flags &= ~kReadSourceSuspended; - LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); + LogVerbose(@"dispatch_resume(readSource)"); + dispatch_resume(readSource); - flags |= kSocketCanAcceptBytes; - flags |= kWriteSourceSuspended; + flags |= kSocketCanAcceptBytes; + flags |= kWriteSourceSuspended; } - (BOOL)usingCFStreamForTLS { - #if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) + { + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - return YES; - } + return YES; + } - #endif +#endif - return NO; + return NO; } - (BOOL)usingSecureTransportForTLS { - // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) + // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable) - #if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE - if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) - { - // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. + if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS)) + { + // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag. - return NO; - } + return NO; + } - #endif +#endif - return YES; + return YES; } - (void)suspendReadSource { - if (!(flags & kReadSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(readSource)"); + if (!(flags & kReadSourceSuspended)) + { + LogVerbose(@"dispatch_suspend(readSource)"); - dispatch_suspend(readSource); - flags |= kReadSourceSuspended; - } + dispatch_suspend(readSource); + flags |= kReadSourceSuspended; + } } - (void)resumeReadSource { - if (flags & kReadSourceSuspended) - { - LogVerbose(@"dispatch_resume(readSource)"); + if (flags & kReadSourceSuspended) + { + LogVerbose(@"dispatch_resume(readSource)"); - dispatch_resume(readSource); - flags &= ~kReadSourceSuspended; - } + dispatch_resume(readSource); + flags &= ~kReadSourceSuspended; + } } - (void)suspendWriteSource { - if (!(flags & kWriteSourceSuspended)) - { - LogVerbose(@"dispatch_suspend(writeSource)"); + if (!(flags & kWriteSourceSuspended)) + { + LogVerbose(@"dispatch_suspend(writeSource)"); - dispatch_suspend(writeSource); - flags |= kWriteSourceSuspended; - } + dispatch_suspend(writeSource); + flags |= kWriteSourceSuspended; + } } - (void)resumeWriteSource { - if (flags & kWriteSourceSuspended) - { - LogVerbose(@"dispatch_resume(writeSource)"); + if (flags & kWriteSourceSuspended) + { + LogVerbose(@"dispatch_resume(writeSource)"); - dispatch_resume(writeSource); - flags &= ~kWriteSourceSuspended; - } + dispatch_resume(writeSource); + flags &= ~kWriteSourceSuspended; + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4444,7 +4553,7 @@ - (void)resumeWriteSource - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag { - [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; + [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; } - (void)readDataWithTimeout:(NSTimeInterval)timeout @@ -4452,7 +4561,7 @@ - (void)readDataWithTimeout:(NSTimeInterval)timeout bufferOffset:(NSUInteger)offset tag:(long)tag { - [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; + [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; } - (void)readDataWithTimeout:(NSTimeInterval)timeout @@ -4461,37 +4570,37 @@ - (void)readDataWithTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag { - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:length - timeout:timeout - readLength:0 - terminator:nil - tag:tag]; + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:length + timeout:timeout + readLength:0 + terminator:nil + tag:tag]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, ^{ @autoreleasepool { - LogTrace(); + LogTrace(); - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) + { + [self->readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. } - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag { - [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; + [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; } - (void)readDataToLength:(NSUInteger)length @@ -4500,41 +4609,41 @@ - (void)readDataToLength:(NSUInteger)length bufferOffset:(NSUInteger)offset tag:(long)tag { - if (length == 0) { - LogWarn(@"Cannot read: length == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } + if (length == 0) { + LogWarn(@"Cannot read: length == 0"); + return; + } + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:0 - timeout:timeout - readLength:length - terminator:nil - tag:tag]; + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:0 + timeout:timeout + readLength:length + terminator:nil + tag:tag]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, ^{ @autoreleasepool { - LogTrace(); + LogTrace(); - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) + { + [self->readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. } - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag { - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; } - (void)readDataToData:(NSData *)data @@ -4543,12 +4652,12 @@ - (void)readDataToData:(NSData *)data bufferOffset:(NSUInteger)offset tag:(long)tag { - [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; + [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; } - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag { - [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; + [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; } - (void)readDataToData:(NSData *)data @@ -4558,84 +4667,84 @@ - (void)readDataToData:(NSData *)data maxLength:(NSUInteger)maxLength tag:(long)tag { - if ([data length] == 0) { - LogWarn(@"Cannot read: [data length] == 0"); - return; - } - if (offset > [buffer length]) { - LogWarn(@"Cannot read: offset > [buffer length]"); - return; - } - if (maxLength > 0 && maxLength < [data length]) { - LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); - return; - } - - GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer - startOffset:offset - maxLength:maxLength - timeout:timeout - readLength:0 - terminator:data - tag:tag]; - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - LogTrace(); - - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self maybeDequeueRead]; - } - }}); - - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. + if ([data length] == 0) { + LogWarn(@"Cannot read: [data length] == 0"); + return; + } + if (offset > [buffer length]) { + LogWarn(@"Cannot read: offset > [buffer length]"); + return; + } + if (maxLength > 0 && maxLength < [data length]) { + LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]"); + return; + } + + GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer + startOffset:offset + maxLength:maxLength + timeout:timeout + readLength:0 + terminator:data + tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) + { + [self->readQueue addObject:packet]; + [self maybeDequeueRead]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. } - (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr { - __block float result = 0.0F; + __block float result = 0.0F; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]]) - { - // We're not reading anything right now. + if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]]) + { + // We're not reading anything right now. - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; + if (tagPtr != NULL) *tagPtr = 0; + if (donePtr != NULL) *donePtr = 0; + if (totalPtr != NULL) *totalPtr = 0; - result = NAN; - } - else - { - // It's only possible to know the progress of our read if we're reading to a certain length. - // If we're reading to data, we of course have no idea when the data will arrive. - // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. + result = NAN; + } + else + { + // It's only possible to know the progress of our read if we're reading to a certain length. + // If we're reading to data, we of course have no idea when the data will arrive. + // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. - NSUInteger done = self->currentRead->bytesDone; - NSUInteger total = self->currentRead->readLength; + NSUInteger done = self->currentRead->bytesDone; + NSUInteger total = self->currentRead->readLength; - if (tagPtr != NULL) *tagPtr = self->currentRead->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; + if (tagPtr != NULL) *tagPtr = self->currentRead->tag; + if (donePtr != NULL) *donePtr = done; + if (totalPtr != NULL) *totalPtr = total; - if (total > 0) - result = (float)done / (float)total; - else - result = 1.0F; - } - }; + if (total > 0) + result = (float)done / (float)total; + else + result = 1.0F; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } /** @@ -4647,2819 +4756,2907 @@ - (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)doneP * - immediately after the socket opens to handle any pending requests * * This method also handles auto-disconnect post read/write completion. -**/ + **/ - (void)maybeDequeueRead { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // If we're not currently processing a read AND we have an available read stream - if ((currentRead == nil) && (flags & kConnected)) - { - if ([readQueue count] > 0) - { - // Dequeue the next object in the write queue - currentRead = [readQueue objectAtIndex:0]; - [readQueue removeObjectAtIndex:0]; - - - if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingReadTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncReadPacket"); - - // Setup read timer (if needed) - [self setupReadTimerWithTimeout:currentRead->timeout]; - - // Immediately read, if possible - [self doReadData]; - } - } - else if (flags & kDisconnectAfterReads) - { - if (flags & kDisconnectAfterWrites) - { - if (([writeQueue count] == 0) && (currentWrite == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - else if (flags & kSocketSecure) - { - [self flushSSLBuffers]; - - // Edge case: - // - // We just drained all data from the ssl buffers, - // and all known data from the socket (socketFDBytesAvailable). - // - // If we didn't get any data from this process, - // then we may have reached the end of the TCP stream. - // - // Be sure callbacks are enabled so we're notified about a disconnection. - - if ([preBuffer availableBytes] == 0) - { - if ([self usingCFStreamForTLS]) { - // Callbacks never disabled - } - else { - [self resumeReadSource]; - } - } - } - } -} + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + // If we're not currently processing a read AND we have an available read stream + if ((currentRead == nil) && (flags & kConnected)) + { + if ([readQueue count] > 0) + { + // Dequeue the next object in the write queue + currentRead = [readQueue objectAtIndex:0]; + [readQueue removeObjectAtIndex:0]; + + + if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]]) + { + LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); + + // Attempt to start TLS + flags |= kStartingReadTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + LogVerbose(@"Dequeued GCDAsyncReadPacket"); + + // Setup read timer (if needed) + [self setupReadTimerWithTimeout:currentRead->timeout]; + + // Immediately read, if possible + [self doReadData]; + } + } + else if (flags & kDisconnectAfterReads) + { + if (flags & kDisconnectAfterWrites) + { + if (([writeQueue count] == 0) && (currentWrite == nil)) + { + [self closeWithError:nil]; + } + } + else + { + [self closeWithError:nil]; + } + } + else if (flags & kSocketSecure) + { + [self flushSSLBuffers]; + + // Edge case: + // + // We just drained all data from the ssl buffers, + // and all known data from the socket (socketFDBytesAvailable). + // + // If we didn't get any data from this process, + // then we may have reached the end of the TCP stream. + // + // Be sure callbacks are enabled so we're notified about a disconnection. + + if ([preBuffer availableBytes] == 0) + { + if ([self usingCFStreamForTLS]) { + // Callbacks never disabled + } + else { + [self resumeReadSource]; + } + } + } + } +} - (void)flushSSLBuffers { - LogTrace(); + LogTrace(); + + NSAssert((flags & kSocketSecure), + @"Cannot flush ssl buffers on non-secure socket"); + + if ([preBuffer availableBytes] > 0) + { + // Only flush the ssl buffers if the prebuffer is empty. + // This is to avoid growing the prebuffer inifinitely large. + + return; + } + +#if TARGET_OS_IPHONE + + if ([self usingCFStreamForTLS]) + { + if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) + { + LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + + CFIndex defaultBytesToRead = (1024 * 4); + + [preBuffer ensureCapacityForWrite:defaultBytesToRead]; + + uint8_t *buffer = [preBuffer writeBuffer]; + + CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); + LogVerbose(@"%@ - CFReadStreamRead(): result = %i", + THIS_METHOD, + (int)result); + + if (result > 0) + { + [preBuffer didWrite:result]; + } + + flags &= ~kSecureSocketHasBytesAvailable; + } + + return; + } + +#endif + + __block NSUInteger estimatedBytesAvailable = 0; + + dispatch_block_t updateEstimatedBytesAvailable = ^{ + + // Figure out if there is any data available to be read + // + // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket + // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket + // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered + // + // We call the variable "estimated" because we don't know how many decrypted bytes we'll get + // from the encrypted bytes in the sslPreBuffer. + // However, we do know this is an upper bound on the estimation. + + estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes]; + + size_t sslInternalBufSize = 0; + SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize); + + estimatedBytesAvailable += sslInternalBufSize; + }; + + updateEstimatedBytesAvailable(); + + if (estimatedBytesAvailable > 0) + { + LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + + BOOL done = NO; + do + { + LogVerbose(@"%@ - estimatedBytesAvailable = %lu", + THIS_METHOD, + (unsigned long)estimatedBytesAvailable); + + // Make sure there's enough room in the prebuffer + + [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; + + // Read data into prebuffer + + uint8_t *buffer = [preBuffer writeBuffer]; + size_t bytesRead = 0; + + OSStatus result = SSLRead(sslContext, + buffer, + (size_t)estimatedBytesAvailable, + &bytesRead); + LogVerbose(@"%@ - read from secure socket = %u", + THIS_METHOD, + (unsigned)bytesRead); + + if (bytesRead > 0) + { + [preBuffer didWrite:bytesRead]; + } + + LogVerbose(@"%@ - prebuffer.length = %zu", + THIS_METHOD, + [preBuffer availableBytes]); + + if (result != noErr) + { + done = YES; + } + else + { + updateEstimatedBytesAvailable(); + } + + } while (!done && estimatedBytesAvailable > 0); + } +} + +- (void)doReadData +{ + LogTrace(); + + // This method is called on the socketQueue. + // It might be called directly, or via the readSource when data is available to be read. + + if ((currentRead == nil) || (flags & kReadsPaused)) + { + LogVerbose(@"No currentRead or kReadsPaused"); + + // Unable to read at this time + + if (flags & kSocketSecure) + { + // Here's the situation: + // + // We have an established secure connection. + // There may not be a currentRead, but there might be encrypted data sitting around for us. + // When the user does get around to issuing a read, that encrypted data will need to be decrypted. + // + // So why make the user wait? + // We might as well get a head start on decrypting some data now. + // + // The other reason we do this has to do with detecting a socket disconnection. + // The SSL/TLS protocol has it's own disconnection handshake. + // So when a secure socket is closed, a "goodbye" packet comes across the wire. + // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. + + [self flushSSLBuffers]; + } + + if ([self usingCFStreamForTLS]) + { + // CFReadStream only fires once when there is available data. + // It won't fire again until we've invoked CFReadStreamRead. + } + else + { + // If the readSource is firing, we need to pause it + // or else it will continue to fire over and over again. + // + // If the readSource is not firing, + // we want it to continue monitoring the socket. + + if (socketFDBytesAvailable > 0) + { + [self suspendReadSource]; + } + } + return; + } + + BOOL hasBytesAvailable = NO; + unsigned long estimatedBytesAvailable = 0; + + if ([self usingCFStreamForTLS]) + { +#if TARGET_OS_IPHONE + + // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) + + estimatedBytesAvailable = 0; + if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) + hasBytesAvailable = YES; + else + hasBytesAvailable = NO; + +#endif + } + else + { + estimatedBytesAvailable = socketFDBytesAvailable; + + if (flags & kSocketSecure) + { + // There are 2 buffers to be aware of here. + // + // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. + // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. + // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. + // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. + // + // The first buffer is one we create. + // SecureTransport often requests small amounts of data. + // This has to do with the encypted packets that are coming across the TCP stream. + // But it's non-optimal to do a bunch of small reads from the BSD socket. + // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) + // and may store excess in the sslPreBuffer. + + estimatedBytesAvailable += [sslPreBuffer availableBytes]; + + // The second buffer is within SecureTransport. + // As mentioned earlier, there are encrypted packets coming across the TCP stream. + // SecureTransport needs the entire packet to decrypt it. + // But if the entire packet produces X bytes of decrypted data, + // and we only asked SecureTransport for X/2 bytes of data, + // it must store the extra X/2 bytes of decrypted data for the next read. + // + // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. + // From the documentation: + // + // "This function does not block or cause any low-level read operations to occur." + + size_t sslInternalBufSize = 0; + SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); + + estimatedBytesAvailable += sslInternalBufSize; + } + + hasBytesAvailable = (estimatedBytesAvailable > 0); + } + + if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) + { + LogVerbose(@"No data available to read..."); + + // No data available to read. + + if (![self usingCFStreamForTLS]) + { + // Need to wait for readSource to fire and notify us of + // available data in the socket's internal read buffer. + + [self resumeReadSource]; + } + return; + } + + if (flags & kStartingReadTLS) + { + LogVerbose(@"Waiting for SSL/TLS handshake to complete"); + + // The readQueue is waiting for SSL/TLS handshake to complete. + + if (flags & kStartingWriteTLS) + { + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) + { + // We are in the process of a SSL Handshake. + // We were waiting for incoming data which has just arrived. + + [self ssl_continueSSLHandshake]; + } + } + else + { + // We are still waiting for the writeQueue to drain and start the SSL/TLS process. + // We now know data is available to read. + + if (![self usingCFStreamForTLS]) + { + // Suspend the read source or else it will continue to fire nonstop. + + [self suspendReadSource]; + } + } + + return; + } + + BOOL done = NO; // Completed read operation + NSError *error = nil; // Error occurred + + NSUInteger totalBytesReadForCurrentRead = 0; + + // + // STEP 1 - READ FROM PREBUFFER + // + + if ([preBuffer availableBytes] > 0) + { + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + NSUInteger bytesToCopy; + + if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; + } + else + { + // Read type #1 or #2 + + bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; + } + + // Make sure we have enough room in the buffer for our read. + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; + + // Copy bytes from prebuffer into packet buffer + + uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; + + memcpy(buffer, [preBuffer readBuffer], bytesToCopy); + + // Remove the copied bytes from the preBuffer + [preBuffer didRead:bytesToCopy]; + + LogVerbose(@"copied(%lu) preBufferLength(%zu)", + (unsigned long)bytesToCopy, + [preBuffer availableBytes]); + + // Update totals + + currentRead->bytesDone += bytesToCopy; + totalBytesReadForCurrentRead += bytesToCopy; + + // Check to see if the read operation is done + + if (currentRead->readLength > 0) + { + // Read type #2 - read a specific length of data + + done = (currentRead->bytesDone == currentRead->readLength); + } + else if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator - NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket"); + // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - if ([preBuffer availableBytes] > 0) - { - // Only flush the ssl buffers if the prebuffer is empty. - // This is to avoid growing the prebuffer inifinitely large. + if (!done && currentRead->maxLength > 0) + { + // We're not done and there's a set maxLength. + // Have we reached that maxLength yet? - return; - } + if (currentRead->bytesDone >= currentRead->maxLength) + { + error = [self readMaxedOutError]; + } + } + } + else + { + // Read type #1 - read all available data + // + // We're done as soon as + // - we've read all available data (in prebuffer and socket) + // - we've read the maxLength of read packet. + + done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); + } + + } + + // + // STEP 2 - READ FROM SOCKET + // + + BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) + BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more + + if (!done && !error && !socketEOF && hasBytesAvailable) + { + NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); + + BOOL readIntoPreBuffer = NO; + uint8_t *buffer = NULL; + size_t bytesRead = 0; + + if (flags & kSocketSecure) + { + if ([self usingCFStreamForTLS]) + { +#if TARGET_OS_IPHONE + + // Using CFStream, rather than SecureTransport, for TLS + + NSUInteger defaultReadLength = (1024 * 32); + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + + CFIndex result = CFReadStreamRead(readStream, + buffer, + (CFIndex)bytesToRead); + LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); + + if (result < 0) + { + error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); + } + else if (result == 0) + { + socketEOF = YES; + } + else + { + waiting = YES; + bytesRead = (size_t)result; + } + + // We only know how many decrypted bytes were read. + // The actual number of bytes read was likely more due to the overhead of the encryption. + // So we reset our flag, and rely on the next callback to alert us of more data. + flags &= ~kSecureSocketHasBytesAvailable; + +#endif + } + else + { + // Using SecureTransport for TLS + // + // We know: + // - how many bytes are available on the socket + // - how many encrypted bytes are sitting in the sslPreBuffer + // - how many decypted bytes are sitting in the sslContext + // + // But we do NOT know: + // - how many encypted bytes are sitting in the sslContext + // + // So we play the regular game of using an upper bound instead. + + NSUInteger defaultReadLength = (1024 * 32); + + if (defaultReadLength < estimatedBytesAvailable) { + defaultReadLength = estimatedBytesAvailable + (1024 * 16); + } + + NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength + shouldPreBuffer:&readIntoPreBuffer]; + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // The documentation from Apple states: + // + // "a read operation might return errSSLWouldBlock, + // indicating that less data than requested was actually transferred" + // + // However, starting around 10.7, the function will sometimes return noErr, + // even if it didn't read as much data as requested. So we need to watch out for that. - #if TARGET_OS_IPHONE + OSStatus result; + do + { + void *loop_buffer = buffer + bytesRead; + size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; + size_t loop_bytesRead = 0; + + result = SSLRead(sslContext, + loop_buffer, + loop_bytesToRead, + &loop_bytesRead); + LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); + + bytesRead += loop_bytesRead; + + } while ((result == noErr) && (bytesRead < bytesToRead)); + + + if (result != noErr) + { + if (result == errSSLWouldBlock) + waiting = YES; + else + { + if (result == errSSLClosedGraceful || result == errSSLClosedAbort) + { + // We've reached the end of the stream. + // Handle this the same way we would an EOF from the socket. + socketEOF = YES; + sslErrCode = result; + } + else + { + error = [self sslError:result]; + } + } + // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. + // This happens when the SSLRead function is able to read some data, + // but not the entire amount we requested. + + if (bytesRead <= 0) + { + bytesRead = 0; + } + } + + // Do not modify socketFDBytesAvailable. + // It will be updated via the SSLReadFunction(). + } + } + else + { + // Normal socket operation + + NSUInteger bytesToRead; + + // There are 3 types of read packets: + // + // 1) Read all available data. + // 2) Read a specific length of data. + // 3) Read up to a particular terminator. + + if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator + + bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable + shouldPreBuffer:&readIntoPreBuffer]; + } + else + { + // Read type #1 or #2 + + bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; + } + + if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) + bytesToRead = SIZE_MAX; + } + + // Make sure we have enough room in the buffer for our read. + // + // We are either reading directly into the currentRead->buffer, + // or we're reading into the temporary preBuffer. + + if (readIntoPreBuffer) + { + [preBuffer ensureCapacityForWrite:bytesToRead]; + + buffer = [preBuffer writeBuffer]; + } + else + { + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; + + buffer = (uint8_t *)[currentRead->buffer mutableBytes] + + currentRead->startOffset + + currentRead->bytesDone; + } + + // Read data into buffer + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); + LogVerbose(@"read from socket = %i", (int)result); + + if (result < 0) + { + if (errno == EWOULDBLOCK) + waiting = YES; + else + error = [self errorWithErrno:errno reason:@"Error in read() function"]; + + socketFDBytesAvailable = 0; + } + else if (result == 0) + { + socketEOF = YES; + socketFDBytesAvailable = 0; + } + else + { + bytesRead = result; + + if (bytesRead < bytesToRead) + { + // The read returned less data than requested. + // This means socketFDBytesAvailable was a bit off due to timing, + // because we read from the socket right when the readSource event was firing. + socketFDBytesAvailable = 0; + } + else + { + if (socketFDBytesAvailable <= bytesRead) + socketFDBytesAvailable = 0; + else + socketFDBytesAvailable -= bytesRead; + } + + if (socketFDBytesAvailable == 0) + { + waiting = YES; + } + } + } + + if (bytesRead > 0) + { + // Check to see if the read operation is done + + if (currentRead->readLength > 0) + { + // Read type #2 - read a specific length of data + // + // Note: We should never be using a prebuffer when we're reading a specific length of data. + + NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + + done = (currentRead->bytesDone == currentRead->readLength); + } + else if (currentRead->term != nil) + { + // Read type #3 - read up to a terminator - if ([self usingCFStreamForTLS]) - { - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + if (readIntoPreBuffer) + { + // We just read a big chunk of data into the preBuffer + + [preBuffer didWrite:bytesRead]; + LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", + [preBuffer availableBytes]); + + // Search for the terminating sequence + + NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; + LogVerbose(@"copying %lu bytes from preBuffer", + (unsigned long)bytesToCopy); + + // Ensure there's room on the read packet's buffer + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; + + // Copy bytes from prebuffer into read buffer + + uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; - CFIndex defaultBytesToRead = (1024 * 4); + memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); - [preBuffer ensureCapacityForWrite:defaultBytesToRead]; + // Remove the copied bytes from the prebuffer + [preBuffer didRead:bytesToCopy]; + LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - uint8_t *buffer = [preBuffer writeBuffer]; + // Update totals + currentRead->bytesDone += bytesToCopy; + totalBytesReadForCurrentRead += bytesToCopy; - CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead); - LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result); + // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above + } + else + { + // We just read a big chunk of data directly into the packet's buffer. + // We need to move any overflow into the prebuffer. + + NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; + + if (overflow == 0) + { + // Perfect match! + // Every byte we read stays in the read buffer, + // and the last byte we read was the last byte of the term. + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + done = YES; + } + else if (overflow > 0) + { + // The term was found within the data that we read, + // and there are extra bytes that extend past the end of the term. + // We need to move these excess bytes out of the read packet and into the prebuffer. + + NSInteger underflow = bytesRead - overflow; + + // Copy excess data into preBuffer + + LogVerbose(@"copying %ld overflow bytes into preBuffer", + (long)overflow); + [preBuffer ensureCapacityForWrite:overflow]; + + uint8_t *overflowBuffer = buffer + underflow; + memcpy([preBuffer writeBuffer], overflowBuffer, overflow); + + [preBuffer didWrite:overflow]; + LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); + + // Note: The completeCurrentRead method will trim the buffer for us. + + currentRead->bytesDone += underflow; + totalBytesReadForCurrentRead += underflow; + done = YES; + } + else + { + // The term was not found within the data that we read. + + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + done = NO; + } + } - if (result > 0) - { - [preBuffer didWrite:result]; - } + if (!done && currentRead->maxLength > 0) + { + // We're not done and there's a set maxLength. + // Have we reached that maxLength yet? + + if (currentRead->bytesDone >= currentRead->maxLength) + { + error = [self readMaxedOutError]; + } + } + } + else + { + // Read type #1 - read all available data - flags &= ~kSecureSocketHasBytesAvailable; - } + if (readIntoPreBuffer) + { + // We just read a chunk of data into the preBuffer + + [preBuffer didWrite:bytesRead]; + + // Now copy the data into the read packet. + // + // Recall that we didn't read directly into the packet's buffer to avoid + // over-allocating memory since we had no clue how much data was available to be read. + // + // Ensure there's room on the read packet's buffer + + [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; + + // Copy bytes from prebuffer into read buffer + + uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + + currentRead->bytesDone; + + memcpy(readBuf, [preBuffer readBuffer], bytesRead); + + // Remove the copied bytes from the prebuffer + [preBuffer didRead:bytesRead]; + + // Update totals + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + } + else + { + currentRead->bytesDone += bytesRead; + totalBytesReadForCurrentRead += bytesRead; + } + + done = YES; + } + + } // if (bytesRead > 0) + + } // if (!done && !error && !socketEOF && hasBytesAvailable) + + + if (!done && currentRead->readLength == 0 && currentRead->term == nil) + { + // Read type #1 - read all available data + // + // We might arrive here if we read data from the prebuffer but not from the socket. + + done = (totalBytesReadForCurrentRead > 0); + } + + // Check to see if we're done, or if we've made progress + + if (done) + { + [self completeCurrentRead]; + + if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) + { + [self maybeDequeueRead]; + } + } + else if (totalBytesReadForCurrentRead > 0) + { + // We're not done read type #2 or #3 yet, but we have read in some bytes + // + // We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is + // possible to reach this point and `waiting` not be set, if the current read's length is + // sufficiently large. In that case, we may have read to some upperbound successfully, but + // that upperbound could be smaller than the desired length. + waiting = YES; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) + { + long theReadTag = currentRead->tag; + + dispatch_async(delegateQueue, + ^{ @autoreleasepool { + + [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; + }}); + } + } + + // Check for errors + + if (error) + { + [self closeWithError:error]; + } + else if (socketEOF) + { + [self doReadEOF]; + } + else if (waiting) + { + if (![self usingCFStreamForTLS]) + { + // Monitor the socket for readability (if we're not already doing so) + [self resumeReadSource]; + } + } + + // Do not add any code here without first adding return statements in the error cases above. +} + +- (void)doReadEOF +{ + LogTrace(); + + // This method may be called more than once. + // If the EOF is read while there is still data in the preBuffer, + // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. + + flags |= kSocketHasReadEOF; + + if (flags & kSocketSecure) + { + // If the SSL layer has any buffered data, flush it into the preBuffer now. + + [self flushSSLBuffers]; + } + + BOOL shouldDisconnect = NO; + NSError *error = nil; + + if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) + { + // We received an EOF during or prior to startTLS. + // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. + + shouldDisconnect = YES; + + if ([self usingSecureTransportForTLS]) + { + error = [self sslError:errSSLClosedAbort]; + } + } + else if (flags & kReadStreamClosed) + { + // The preBuffer has already been drained. + // The config allows half-duplex connections. + // We've previously checked the socket, and it appeared writeable. + // So we marked the read stream as closed and notified the delegate. + // + // As per the half-duplex contract, the socket will be closed when a write fails, + // or when the socket is manually closed. + + shouldDisconnect = NO; + } + else if ([preBuffer availableBytes] > 0) + { + LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); + + // Although we won't be able to read any more data from the socket, + // there is existing data that has been prebuffered that we can read. + + shouldDisconnect = NO; + } + else if (config & kAllowHalfDuplexConnection) + { + // We just received an EOF (end of file) from the socket's read stream. + // This means the remote end of the socket (the peer we're connected to) + // has explicitly stated that it will not be sending us any more data. + // + // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) + + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + + struct pollfd pfd[1]; + pfd[0].fd = socketFD; + pfd[0].events = POLLOUT; + pfd[0].revents = 0; + + poll(pfd, 1, 0); + + if (pfd[0].revents & POLLOUT) + { + // Socket appears to still be writeable + + shouldDisconnect = NO; + flags |= kReadStreamClosed; + + // Notify the delegate that we're going half-duplex + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socketDidCloseReadStream:self]; + }}); + } + } + else + { + shouldDisconnect = YES; + } + } + else + { + shouldDisconnect = YES; + } + + + if (shouldDisconnect) + { + if (error == nil) + { + if ([self usingSecureTransportForTLS]) + { + if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) + { + error = [self sslError:sslErrCode]; + } + else + { + error = [self connectionClosedError]; + } + } + else + { + error = [self connectionClosedError]; + } + } + [self closeWithError:error]; + } + else + { + if (![self usingCFStreamForTLS]) + { + // Suspend the read source (if needed) + + [self suspendReadSource]; + } + } +} + +- (void)completeCurrentRead +{ + LogTrace(); + + NSAssert(currentRead, + @"Trying to complete current read when there is no current read."); + + + NSData *result = nil; + + if (currentRead->bufferOwner) + { + // We created the buffer on behalf of the user. + // Trim our buffer to be the proper size. + [currentRead->buffer setLength:currentRead->bytesDone]; + + result = currentRead->buffer; + } + else + { + // We did NOT create the buffer. + // The buffer is owned by the caller. + // Only trim the buffer if we had to increase its size. + + if ([currentRead->buffer length] > currentRead->originalBufferLength) + { + NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; + NSUInteger origSize = currentRead->originalBufferLength; + + NSUInteger buffSize = MAX(readSize, origSize); + + [currentRead->buffer setLength:buffSize]; + } + + uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; + + result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; + } + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) + { + GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + [theDelegate socket:self didReadData:result withTag:theRead->tag]; + }}); + } + + [self endCurrentRead]; +} + +- (void)endCurrentRead +{ + if (readTimer) + { + dispatch_source_cancel(readTimer); + readTimer = NULL; + } + + currentRead = nil; +} + +- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout +{ + if (timeout >= 0.0) + { + readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + socketQueue); + + __weak GCDAsyncSocket *weakSelf = self; + + dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" + + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; + + [strongSelf doReadTimeout]; + +#pragma clang diagnostic pop + }}); + +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theReadTimer = readTimer; + dispatch_source_set_cancel_handler(readTimer, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" + + LogVerbose(@"dispatch_release(readTimer)"); + dispatch_release(theReadTimer); + +#pragma clang diagnostic pop + }); +#endif + + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeout * NSEC_PER_SEC)); + + dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(readTimer); + } +} + +- (void)doReadTimeout +{ + // This is a little bit tricky. + // Ideally we'd like to synchronously query the delegate about a timeout extension. + // But if we do so synchronously we risk a possible deadlock. + // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. + + flags |= kReadsPaused; + + __strong id theDelegate = delegate; + + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) + { + GCDAsyncReadPacket *theRead = currentRead; + + dispatch_async(delegateQueue, ^{ @autoreleasepool { + + NSTimeInterval timeoutExtension = 0.0; + + timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag + elapsed:theRead->timeout + bytesDone:theRead->bytesDone]; + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + [self doReadTimeoutWithExtension:timeoutExtension]; + }}); + }}); + } + else + { + [self doReadTimeoutWithExtension:0.0]; + } +} + +- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension +{ + if (currentRead) + { + if (timeoutExtension > 0.0) + { + currentRead->timeout += timeoutExtension; + + // Reschedule the timer + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeoutExtension * NSEC_PER_SEC)); + dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); + + // Unpause reads, and continue + flags &= ~kReadsPaused; + [self doReadData]; + } + else + { + LogVerbose(@"ReadTimeout"); + + [self closeWithError:[self readTimeoutError]]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Writing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +{ + if ([data length] == 0) return; + + GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; + + dispatch_async(socketQueue, ^{ @autoreleasepool { + + LogTrace(); + + if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) + { + [self->writeQueue addObject:packet]; + [self maybeDequeueWrite]; + } + }}); + + // Do not rely on the block being run in order to release the packet, + // as the queue might get released without the block completing. +} + +- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr +{ + __block float result = 0.0F; + + dispatch_block_t block = ^{ + + if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) + { + // We're not writing anything right now. + + if (tagPtr != NULL) *tagPtr = 0; + if (donePtr != NULL) *donePtr = 0; + if (totalPtr != NULL) *totalPtr = 0; + + result = NAN; + } + else + { + NSUInteger done = self->currentWrite->bytesDone; + NSUInteger total = [self->currentWrite->buffer length]; + + if (tagPtr != NULL) *tagPtr = self->currentWrite->tag; + if (donePtr != NULL) *donePtr = done; + if (totalPtr != NULL) *totalPtr = total; + + result = (float)done / (float)total; + } + }; + + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); + + return result; +} + +/** + * Conditionally starts a new write. + * + * It is called when: + * - a user requests a write + * - after a write request has finished (to handle the next request) + * - immediately after the socket opens to handle any pending requests + * + * This method also handles auto-disconnect post read/write completion. + **/ +- (void)maybeDequeueWrite +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + + // If we're not currently processing a write AND we have an available write stream + if ((currentWrite == nil) && (flags & kConnected)) + { + if ([writeQueue count] > 0) + { + // Dequeue the next object in the write queue + currentWrite = [writeQueue objectAtIndex:0]; + [writeQueue removeObjectAtIndex:0]; + + + if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) + { + LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); + + // Attempt to start TLS + flags |= kStartingWriteTLS; + + // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set + [self maybeStartTLS]; + } + else + { + LogVerbose(@"Dequeued GCDAsyncWritePacket"); + + // Setup write timer (if needed) + [self setupWriteTimerWithTimeout:currentWrite->timeout]; + + // Immediately write, if possible + [self doWriteData]; + } + } + else if (flags & kDisconnectAfterWrites) + { + if (flags & kDisconnectAfterReads) + { + if (([readQueue count] == 0) && (currentRead == nil)) + { + [self closeWithError:nil]; + } + } + else + { + [self closeWithError:nil]; + } + } + } +} + +- (void)doWriteData +{ + LogTrace(); + + // This method is called by the writeSource via the socketQueue + + if ((currentWrite == nil) || (flags & kWritesPaused)) + { + LogVerbose(@"No currentWrite or kWritesPaused"); + + // Unable to write at this time + + if ([self usingCFStreamForTLS]) + { + // CFWriteStream only fires once when there is available data. + // It won't fire again until we've invoked CFWriteStreamWrite. + } + else + { + // If the writeSource is firing, we need to pause it + // or else it will continue to fire over and over again. + + if (flags & kSocketCanAcceptBytes) + { + [self suspendWriteSource]; + } + } + return; + } + + if (!(flags & kSocketCanAcceptBytes)) + { + LogVerbose(@"No space available to write..."); + + // No space available to write. + + if (![self usingCFStreamForTLS]) + { + // Need to wait for writeSource to fire and notify us of + // available space in the socket's internal write buffer. + + [self resumeWriteSource]; + } + return; + } + + if (flags & kStartingWriteTLS) + { + LogVerbose(@"Waiting for SSL/TLS handshake to complete"); + + // The writeQueue is waiting for SSL/TLS handshake to complete. + + if (flags & kStartingReadTLS) + { + if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) + { + // We are in the process of a SSL Handshake. + // We were waiting for available space in the socket's internal OS buffer to continue writing. + + [self ssl_continueSSLHandshake]; + } + } + else + { + // We are still waiting for the readQueue to drain and start the SSL/TLS process. + // We now know we can write to the socket. + + if (![self usingCFStreamForTLS]) + { + // Suspend the write source or else it will continue to fire nonstop. + + [self suspendWriteSource]; + } + } + + return; + } + + // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) + + BOOL waiting = NO; + NSError *error = nil; + size_t bytesWritten = 0; + + if (flags & kSocketSecure) + { + if ([self usingCFStreamForTLS]) + { +#if TARGET_OS_IPHONE + + // + // Writing data using CFStream (over internal TLS) + // + + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; + + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; + + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } + + CFIndex result = CFWriteStreamWrite(writeStream, + buffer, + (CFIndex)bytesToWrite); + LogVerbose(@"CFWriteStreamWrite(%lu) = %li", + (unsigned long)bytesToWrite, + result); + + if (result < 0) + { + error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); + } + else + { + bytesWritten = (size_t)result; + + // We always set waiting to true in this scenario. + // CFStream may have altered our underlying socket to non-blocking. + // Thus if we attempt to write without a callback, we may end up blocking our queue. + waiting = YES; + } + +#endif + } + else + { + // We're going to use the SSLWrite function. + // + // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) + // + // Parameters: + // context - An SSL session context reference. + // data - A pointer to the buffer of data to write. + // dataLength - The amount, in bytes, of data to write. + // processed - On return, the length, in bytes, of the data actually written. + // + // It sounds pretty straight-forward, + // but there are a few caveats you should be aware of. + // + // The SSLWrite method operates in a non-obvious (and rather annoying) manner. + // According to the documentation: + // + // Because you may configure the underlying connection to operate in a non-blocking manner, + // a write operation might return errSSLWouldBlock, indicating that less data than requested + // was actually transferred. In this case, you should repeat the call to SSLWrite until some + // other result is returned. + // + // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, + // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), + // but it sets processed to dataLength !! + // + // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, + // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to + // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. + // + // You might be wondering: + // If the SSLWrite function doesn't tell us how many bytes were written, + // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) + // for the next time we invoke SSLWrite? + // + // The answer is that SSLWrite cached all the data we told it to write, + // and it will push out that data next time we call SSLWrite. + // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. + // If we call SSLWrite with empty data, then it will simply push out the cached data. + // + // For this purpose we're going to break large writes into a series of smaller writes. + // This allows us to report progress back to the delegate. + + OSStatus result; + + BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); + BOOL hasNewDataToWrite = YES; + + if (hasCachedDataToWrite) + { + size_t processed = 0; + + result = SSLWrite(sslContext, NULL, 0, &processed); + + if (result == noErr) + { + bytesWritten = sslWriteCachedLength; + sslWriteCachedLength = 0; + + if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) + { + // We've written all data for the current write. + hasNewDataToWrite = NO; + } + } + else + { + if (result == errSSLWouldBlock) + { + waiting = YES; + } + else + { + error = [self sslError:result]; + } + + // Can't write any new data since we were unable to write the cached data. + hasNewDataToWrite = NO; + } + } + + if (hasNewDataToWrite) + { + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + + currentWrite->bytesDone + + bytesWritten; + + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; + + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } + + size_t bytesRemaining = bytesToWrite; + + BOOL keepLooping = YES; + while (keepLooping) + { + const size_t sslMaxBytesToWrite = 32768; + size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); + size_t sslBytesWritten = 0; + + result = SSLWrite(sslContext, + buffer, + sslBytesToWrite, + &sslBytesWritten); + + if (result == noErr) + { + buffer += sslBytesWritten; + bytesWritten += sslBytesWritten; + bytesRemaining -= sslBytesWritten; + + keepLooping = (bytesRemaining > 0); + } + else + { + if (result == errSSLWouldBlock) + { + waiting = YES; + sslWriteCachedLength = sslBytesToWrite; + } + else + { + error = [self sslError:result]; + } - return; - } + keepLooping = NO; + } - #endif + } // while (keepLooping) - __block NSUInteger estimatedBytesAvailable = 0; + } // if (hasNewDataToWrite) + } + } + else + { + // + // Writing data directly over raw socket + // - dispatch_block_t updateEstimatedBytesAvailable = ^{ + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - // Figure out if there is any data available to be read - // - // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket - // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket - // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered - // - // We call the variable "estimated" because we don't know how many decrypted bytes we'll get - // from the encrypted bytes in the sslPreBuffer. - // However, we do know this is an upper bound on the estimation. + const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes]; + NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize); + if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) + { + bytesToWrite = SIZE_MAX; + } - estimatedBytesAvailable += sslInternalBufSize; - }; + ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); + LogVerbose(@"wrote to socket = %zd", result); - updateEstimatedBytesAvailable(); + // Check results + if (result < 0) + { + if (errno == EWOULDBLOCK) + { + waiting = YES; + } + else + { + error = [self errorWithErrno:errno reason:@"Error in write() function"]; + } + } + else + { + bytesWritten = result; + } + } - if (estimatedBytesAvailable > 0) - { - LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD); + // We're done with our writing. + // If we explictly ran into a situation where the socket told us there was no room in the buffer, + // then we immediately resume listening for notifications. + // + // We must do this before we dequeue another write, + // as that may in turn invoke this method again. + // + // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - BOOL done = NO; - do - { - LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable); + if (waiting) + { + flags &= ~kSocketCanAcceptBytes; - // Make sure there's enough room in the prebuffer + if (![self usingCFStreamForTLS]) + { + [self resumeWriteSource]; + } + } - [preBuffer ensureCapacityForWrite:estimatedBytesAvailable]; + // Check our results - // Read data into prebuffer + BOOL done = NO; - uint8_t *buffer = [preBuffer writeBuffer]; - size_t bytesRead = 0; + if (bytesWritten > 0) + { + // Update total amount read for the current write + currentWrite->bytesDone += bytesWritten; + LogVerbose(@"currentWrite->bytesDone = %lu", + (unsigned long)currentWrite->bytesDone); - OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead); - LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead); + // Is packet done? + done = (currentWrite->bytesDone == [currentWrite->buffer length]); + } - if (bytesRead > 0) - { - [preBuffer didWrite:bytesRead]; - } + if (done) + { + [self completeCurrentWrite]; - LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]); + if (!error) + { + dispatch_async(socketQueue, ^{ @autoreleasepool{ - if (result != noErr) - { - done = YES; - } - else - { - updateEstimatedBytesAvailable(); - } + [self maybeDequeueWrite]; + }}); + } + } + else + { + // We were unable to finish writing the data, + // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - } while (!done && estimatedBytesAvailable > 0); - } -} + if (!waiting && !error) + { + // This would be the case if our write was able to accept some data, but not all of it. -- (void)doReadData -{ - LogTrace(); - - // This method is called on the socketQueue. - // It might be called directly, or via the readSource when data is available to be read. - - if ((currentRead == nil) || (flags & kReadsPaused)) - { - LogVerbose(@"No currentRead or kReadsPaused"); - - // Unable to read at this time - - if (flags & kSocketSecure) - { - // Here's the situation: - // - // We have an established secure connection. - // There may not be a currentRead, but there might be encrypted data sitting around for us. - // When the user does get around to issuing a read, that encrypted data will need to be decrypted. - // - // So why make the user wait? - // We might as well get a head start on decrypting some data now. - // - // The other reason we do this has to do with detecting a socket disconnection. - // The SSL/TLS protocol has it's own disconnection handshake. - // So when a secure socket is closed, a "goodbye" packet comes across the wire. - // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection. - - [self flushSSLBuffers]; - } - - if ([self usingCFStreamForTLS]) - { - // CFReadStream only fires once when there is available data. - // It won't fire again until we've invoked CFReadStreamRead. - } - else - { - // If the readSource is firing, we need to pause it - // or else it will continue to fire over and over again. - // - // If the readSource is not firing, - // we want it to continue monitoring the socket. - - if (socketFDBytesAvailable > 0) - { - [self suspendReadSource]; - } - } - return; - } - - BOOL hasBytesAvailable = NO; - unsigned long estimatedBytesAvailable = 0; - - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS) - - estimatedBytesAvailable = 0; - if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream)) - hasBytesAvailable = YES; - else - hasBytesAvailable = NO; - - #endif - } - else - { - estimatedBytesAvailable = socketFDBytesAvailable; - - if (flags & kSocketSecure) - { - // There are 2 buffers to be aware of here. - // - // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP. - // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction. - // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport. - // SecureTransport then decrypts the data, and finally returns the decrypted data back to us. - // - // The first buffer is one we create. - // SecureTransport often requests small amounts of data. - // This has to do with the encypted packets that are coming across the TCP stream. - // But it's non-optimal to do a bunch of small reads from the BSD socket. - // So our SSLReadFunction reads all available data from the socket (optimizing the sys call) - // and may store excess in the sslPreBuffer. - - estimatedBytesAvailable += [sslPreBuffer availableBytes]; - - // The second buffer is within SecureTransport. - // As mentioned earlier, there are encrypted packets coming across the TCP stream. - // SecureTransport needs the entire packet to decrypt it. - // But if the entire packet produces X bytes of decrypted data, - // and we only asked SecureTransport for X/2 bytes of data, - // it must store the extra X/2 bytes of decrypted data for the next read. - // - // The SSLGetBufferedReadSize function will tell us the size of this internal buffer. - // From the documentation: - // - // "This function does not block or cause any low-level read operations to occur." - - size_t sslInternalBufSize = 0; - SSLGetBufferedReadSize(sslContext, &sslInternalBufSize); - - estimatedBytesAvailable += sslInternalBufSize; - } - - hasBytesAvailable = (estimatedBytesAvailable > 0); - } - - if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0)) - { - LogVerbose(@"No data available to read..."); - - // No data available to read. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. - - [self resumeReadSource]; - } - return; - } - - if (flags & kStartingReadTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The readQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingWriteTLS) - { - if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) - { - // We are in the process of a SSL Handshake. - // We were waiting for incoming data which has just arrived. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the writeQueue to drain and start the SSL/TLS process. - // We now know data is available to read. - - if (![self usingCFStreamForTLS]) - { - // Suspend the read source or else it will continue to fire nonstop. - - [self suspendReadSource]; - } - } - - return; - } - - BOOL done = NO; // Completed read operation - NSError *error = nil; // Error occurred - - NSUInteger totalBytesReadForCurrentRead = 0; - - // - // STEP 1 - READ FROM PREBUFFER - // - - if ([preBuffer availableBytes] > 0) - { - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - NSUInteger bytesToCopy; + flags &= ~kSocketCanAcceptBytes; - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator + if (![self usingCFStreamForTLS]) + { + [self resumeWriteSource]; + } + } - bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - } - else - { - // Read type #1 or #2 + if (bytesWritten > 0) + { + // We're not done with the entire write, but we have written some bytes - bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]]; - } + __strong id theDelegate = delegate; - // Make sure we have enough room in the buffer for our read. + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) + { + long theWriteTag = currentWrite->tag; - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; + dispatch_async(delegateQueue, + ^{ @autoreleasepool { - // Copy bytes from prebuffer into packet buffer + [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; + }}); + } + } + } - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + - currentRead->bytesDone; + // Check for errors - memcpy(buffer, [preBuffer readBuffer], bytesToCopy); + if (error) + { + [self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]]; + } - // Remove the copied bytes from the preBuffer - [preBuffer didRead:bytesToCopy]; + // Do not add any code here without first adding a return statement in the error case above. +} - LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]); +- (void)completeCurrentWrite +{ + LogTrace(); - // Update totals + NSAssert(currentWrite, + @"Trying to complete current write when there is no current write."); - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - // Check to see if the read operation is done + __strong id theDelegate = delegate; - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method - - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? - - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - // - // We're done as soon as - // - we've read all available data (in prebuffer and socket) - // - we've read the maxLength of read packet. - - done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength)); - } - - } - - // - // STEP 2 - READ FROM SOCKET - // - - BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file) - BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more - - if (!done && !error && !socketEOF && hasBytesAvailable) - { - NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic"); - - BOOL readIntoPreBuffer = NO; - uint8_t *buffer = NULL; - size_t bytesRead = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // Using CFStream, rather than SecureTransport, for TLS - - NSUInteger defaultReadLength = (1024 * 32); - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead); - LogVerbose(@"CFReadStreamRead(): result = %i", (int)result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream); - } - else if (result == 0) - { - socketEOF = YES; - } - else - { - waiting = YES; - bytesRead = (size_t)result; - } - - // We only know how many decrypted bytes were read. - // The actual number of bytes read was likely more due to the overhead of the encryption. - // So we reset our flag, and rely on the next callback to alert us of more data. - flags &= ~kSecureSocketHasBytesAvailable; - - #endif - } - else - { - // Using SecureTransport for TLS - // - // We know: - // - how many bytes are available on the socket - // - how many encrypted bytes are sitting in the sslPreBuffer - // - how many decypted bytes are sitting in the sslContext - // - // But we do NOT know: - // - how many encypted bytes are sitting in the sslContext - // - // So we play the regular game of using an upper bound instead. - - NSUInteger defaultReadLength = (1024 * 32); - - if (defaultReadLength < estimatedBytesAvailable) { - defaultReadLength = estimatedBytesAvailable + (1024 * 16); - } - - NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength - shouldPreBuffer:&readIntoPreBuffer]; - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // The documentation from Apple states: - // - // "a read operation might return errSSLWouldBlock, - // indicating that less data than requested was actually transferred" - // - // However, starting around 10.7, the function will sometimes return noErr, - // even if it didn't read as much data as requested. So we need to watch out for that. - - OSStatus result; - do - { - void *loop_buffer = buffer + bytesRead; - size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead; - size_t loop_bytesRead = 0; - - result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead); - LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead); - - bytesRead += loop_bytesRead; - - } while ((result == noErr) && (bytesRead < bytesToRead)); - - - if (result != noErr) - { - if (result == errSSLWouldBlock) - waiting = YES; - else - { - if (result == errSSLClosedGraceful || result == errSSLClosedAbort) - { - // We've reached the end of the stream. - // Handle this the same way we would an EOF from the socket. - socketEOF = YES; - sslErrCode = result; - } - else - { - error = [self sslError:result]; - } - } - // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock. - // This happens when the SSLRead function is able to read some data, - // but not the entire amount we requested. - - if (bytesRead <= 0) - { - bytesRead = 0; - } - } - - // Do not modify socketFDBytesAvailable. - // It will be updated via the SSLReadFunction(). - } - } - else - { - // Normal socket operation - - NSUInteger bytesToRead; - - // There are 3 types of read packets: - // - // 1) Read all available data. - // 2) Read a specific length of data. - // 3) Read up to a particular terminator. - - if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable - shouldPreBuffer:&readIntoPreBuffer]; - } - else - { - // Read type #1 or #2 - - bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable]; - } - - if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3) - bytesToRead = SIZE_MAX; - } - - // Make sure we have enough room in the buffer for our read. - // - // We are either reading directly into the currentRead->buffer, - // or we're reading into the temporary preBuffer. - - if (readIntoPreBuffer) - { - [preBuffer ensureCapacityForWrite:bytesToRead]; - - buffer = [preBuffer writeBuffer]; - } - else - { - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead]; - - buffer = (uint8_t *)[currentRead->buffer mutableBytes] - + currentRead->startOffset - + currentRead->bytesDone; - } - - // Read data into buffer - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - ssize_t result = read(socketFD, buffer, (size_t)bytesToRead); - LogVerbose(@"read from socket = %i", (int)result); - - if (result < 0) - { - if (errno == EWOULDBLOCK) - waiting = YES; - else - error = [self errorWithErrno:errno reason:@"Error in read() function"]; - - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - socketEOF = YES; - socketFDBytesAvailable = 0; - } - else - { - bytesRead = result; - - if (bytesRead < bytesToRead) - { - // The read returned less data than requested. - // This means socketFDBytesAvailable was a bit off due to timing, - // because we read from the socket right when the readSource event was firing. - socketFDBytesAvailable = 0; - } - else - { - if (socketFDBytesAvailable <= bytesRead) - socketFDBytesAvailable = 0; - else - socketFDBytesAvailable -= bytesRead; - } - - if (socketFDBytesAvailable == 0) - { - waiting = YES; - } - } - } - - if (bytesRead > 0) - { - // Check to see if the read operation is done - - if (currentRead->readLength > 0) - { - // Read type #2 - read a specific length of data - // - // Note: We should never be using a prebuffer when we're reading a specific length of data. - - NSAssert(readIntoPreBuffer == NO, @"Invalid logic"); - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - - done = (currentRead->bytesDone == currentRead->readLength); - } - else if (currentRead->term != nil) - { - // Read type #3 - read up to a terminator - - if (readIntoPreBuffer) - { - // We just read a big chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]); - - // Search for the terminating sequence - - NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done]; - LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy); - - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesToCopy); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesToCopy]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Update totals - currentRead->bytesDone += bytesToCopy; - totalBytesReadForCurrentRead += bytesToCopy; - - // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above - } - else - { - // We just read a big chunk of data directly into the packet's buffer. - // We need to move any overflow into the prebuffer. - - NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead]; - - if (overflow == 0) - { - // Perfect match! - // Every byte we read stays in the read buffer, - // and the last byte we read was the last byte of the term. - - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = YES; - } - else if (overflow > 0) - { - // The term was found within the data that we read, - // and there are extra bytes that extend past the end of the term. - // We need to move these excess bytes out of the read packet and into the prebuffer. - - NSInteger underflow = bytesRead - overflow; - - // Copy excess data into preBuffer - - LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow); - [preBuffer ensureCapacityForWrite:overflow]; - - uint8_t *overflowBuffer = buffer + underflow; - memcpy([preBuffer writeBuffer], overflowBuffer, overflow); - - [preBuffer didWrite:overflow]; - LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]); - - // Note: The completeCurrentRead method will trim the buffer for us. - - currentRead->bytesDone += underflow; - totalBytesReadForCurrentRead += underflow; - done = YES; - } - else - { - // The term was not found within the data that we read. + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) + { + long theWriteTag = currentWrite->tag; - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - done = NO; - } - } + dispatch_async(delegateQueue, ^{ @autoreleasepool { - if (!done && currentRead->maxLength > 0) - { - // We're not done and there's a set maxLength. - // Have we reached that maxLength yet? + [theDelegate socket:self didWriteDataWithTag:theWriteTag]; + }}); + } - if (currentRead->bytesDone >= currentRead->maxLength) - { - error = [self readMaxedOutError]; - } - } - } - else - { - // Read type #1 - read all available data - - if (readIntoPreBuffer) - { - // We just read a chunk of data into the preBuffer - - [preBuffer didWrite:bytesRead]; - - // Now copy the data into the read packet. - // - // Recall that we didn't read directly into the packet's buffer to avoid - // over-allocating memory since we had no clue how much data was available to be read. - // - // Ensure there's room on the read packet's buffer - - [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead]; - - // Copy bytes from prebuffer into read buffer - - uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset - + currentRead->bytesDone; - - memcpy(readBuf, [preBuffer readBuffer], bytesRead); - - // Remove the copied bytes from the prebuffer - [preBuffer didRead:bytesRead]; - - // Update totals - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - else - { - currentRead->bytesDone += bytesRead; - totalBytesReadForCurrentRead += bytesRead; - } - - done = YES; - } - - } // if (bytesRead > 0) - - } // if (!done && !error && !socketEOF && hasBytesAvailable) - - - if (!done && currentRead->readLength == 0 && currentRead->term == nil) - { - // Read type #1 - read all available data - // - // We might arrive here if we read data from the prebuffer but not from the socket. - - done = (totalBytesReadForCurrentRead > 0); - } - - // Check to see if we're done, or if we've made progress - - if (done) - { - [self completeCurrentRead]; - - if (!error && (!socketEOF || [preBuffer availableBytes] > 0)) - { - [self maybeDequeueRead]; - } - } - else if (totalBytesReadForCurrentRead > 0) - { - // We're not done read type #2 or #3 yet, but we have read in some bytes - // - // We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is - // possible to reach this point and `waiting` not be set, if the current read's length is - // sufficiently large. In that case, we may have read to some upperbound successfully, but - // that upperbound could be smaller than the desired length. - waiting = YES; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)]) - { - long theReadTag = currentRead->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag]; - }}); - } - } - - // Check for errors - - if (error) - { - [self closeWithError:error]; - } - else if (socketEOF) - { - [self doReadEOF]; - } - else if (waiting) - { - if (![self usingCFStreamForTLS]) - { - // Monitor the socket for readability (if we're not already doing so) - [self resumeReadSource]; - } - } - - // Do not add any code here without first adding return statements in the error cases above. + [self endCurrentWrite]; } -- (void)doReadEOF +- (void)endCurrentWrite { - LogTrace(); - - // This method may be called more than once. - // If the EOF is read while there is still data in the preBuffer, - // then this method may be called continually after invocations of doReadData to see if it's time to disconnect. - - flags |= kSocketHasReadEOF; - - if (flags & kSocketSecure) - { - // If the SSL layer has any buffered data, flush it into the preBuffer now. - - [self flushSSLBuffers]; - } - - BOOL shouldDisconnect = NO; - NSError *error = nil; - - if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS)) - { - // We received an EOF during or prior to startTLS. - // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation. - - shouldDisconnect = YES; - - if ([self usingSecureTransportForTLS]) - { - error = [self sslError:errSSLClosedAbort]; - } - } - else if (flags & kReadStreamClosed) - { - // The preBuffer has already been drained. - // The config allows half-duplex connections. - // We've previously checked the socket, and it appeared writeable. - // So we marked the read stream as closed and notified the delegate. - // - // As per the half-duplex contract, the socket will be closed when a write fails, - // or when the socket is manually closed. - - shouldDisconnect = NO; - } - else if ([preBuffer availableBytes] > 0) - { - LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer"); - - // Although we won't be able to read any more data from the socket, - // there is existing data that has been prebuffered that we can read. - - shouldDisconnect = NO; - } - else if (config & kAllowHalfDuplexConnection) - { - // We just received an EOF (end of file) from the socket's read stream. - // This means the remote end of the socket (the peer we're connected to) - // has explicitly stated that it will not be sending us any more data. - // - // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us) - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - struct pollfd pfd[1]; - pfd[0].fd = socketFD; - pfd[0].events = POLLOUT; - pfd[0].revents = 0; - - poll(pfd, 1, 0); - - if (pfd[0].revents & POLLOUT) - { - // Socket appears to still be writeable - - shouldDisconnect = NO; - flags |= kReadStreamClosed; - - // Notify the delegate that we're going half-duplex - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidCloseReadStream:self]; - }}); - } - } - else - { - shouldDisconnect = YES; - } - } - else - { - shouldDisconnect = YES; - } - - - if (shouldDisconnect) - { - if (error == nil) - { - if ([self usingSecureTransportForTLS]) - { - if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful) - { - error = [self sslError:sslErrCode]; - } - else - { - error = [self connectionClosedError]; - } - } - else - { - error = [self connectionClosedError]; - } - } - [self closeWithError:error]; - } - else - { - if (![self usingCFStreamForTLS]) - { - // Suspend the read source (if needed) - - [self suspendReadSource]; - } - } + if (writeTimer) + { + dispatch_source_cancel(writeTimer); + writeTimer = NULL; + } + + currentWrite = nil; } -- (void)completeCurrentRead +- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout { - LogTrace(); - - NSAssert(currentRead, @"Trying to complete current read when there is no current read."); + if (timeout >= 0.0) + { + writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + socketQueue); + __weak GCDAsyncSocket *weakSelf = self; - NSData *result = nil; - - if (currentRead->bufferOwner) - { - // We created the buffer on behalf of the user. - // Trim our buffer to be the proper size. - [currentRead->buffer setLength:currentRead->bytesDone]; - - result = currentRead->buffer; - } - else - { - // We did NOT create the buffer. - // The buffer is owned by the caller. - // Only trim the buffer if we had to increase its size. - - if ([currentRead->buffer length] > currentRead->originalBufferLength) - { - NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone; - NSUInteger origSize = currentRead->originalBufferLength; - - NSUInteger buffSize = MAX(readSize, origSize); + dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - [currentRead->buffer setLength:buffSize]; - } + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf == nil) return_from_block; - uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset; + [strongSelf doWriteTimeout]; - result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO]; - } +#pragma clang diagnostic pop + }}); - __strong id theDelegate = delegate; +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theWriteTimer = writeTimer; + dispatch_source_set_cancel_handler(writeTimer, ^{ +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)]) - { - GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer + LogVerbose(@"dispatch_release(writeTimer)"); + dispatch_release(theWriteTimer); - dispatch_async(delegateQueue, ^{ @autoreleasepool { +#pragma clang diagnostic pop + }); +#endif - [theDelegate socket:self didReadData:result withTag:theRead->tag]; - }}); - } + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeout * NSEC_PER_SEC)); - [self endCurrentRead]; + dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(writeTimer); + } } -- (void)endCurrentRead +- (void)doWriteTimeout { - if (readTimer) - { - dispatch_source_cancel(readTimer); - readTimer = NULL; - } + // This is a little bit tricky. + // Ideally we'd like to synchronously query the delegate about a timeout extension. + // But if we do so synchronously we risk a possible deadlock. + // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. - currentRead = nil; -} + flags |= kWritesPaused; -- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + __strong id theDelegate = delegate; - __weak GCDAsyncSocket *weakSelf = self; + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) + { + GCDAsyncWritePacket *theWrite = currentWrite; - dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + dispatch_async(delegateQueue, ^{ @autoreleasepool { - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + NSTimeInterval timeoutExtension = 0.0; - [strongSelf doReadTimeout]; + timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag + elapsed:theWrite->timeout + bytesDone:theWrite->bytesDone]; - #pragma clang diagnostic pop - }}); + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theReadTimer = readTimer; - dispatch_source_set_cancel_handler(readTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + [self doWriteTimeoutWithExtension:timeoutExtension]; + }}); + }}); + } + else + { + [self doWriteTimeoutWithExtension:0.0]; + } +} - LogVerbose(@"dispatch_release(readTimer)"); - dispatch_release(theReadTimer); +- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension +{ + if (currentWrite) + { + if (timeoutExtension > 0.0) + { + currentWrite->timeout += timeoutExtension; - #pragma clang diagnostic pop - }); - #endif + // Reschedule the timer + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeoutExtension * NSEC_PER_SEC)); + dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + // Unpause writes, and continue + flags &= ~kWritesPaused; + [self doWriteData]; + } + else + { + LogVerbose(@"WriteTimeout"); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(readTimer); - } + [self closeWithError:[self writeTimeoutError]]; + } + } } -- (void)doReadTimeout +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Security +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)startTLS:(NSDictionary *)tlsSettings { - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. + LogTrace(); - flags |= kReadsPaused; + if (tlsSettings == nil) + { + // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, + // but causes problems if we later try to fetch the remote host's certificate. + // + // To be exact, it causes the following to return NULL instead of the normal result: + // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) + // + // So we use an empty dictionary instead, which works perfectly. - __strong id theDelegate = delegate; + tlsSettings = [NSDictionary dictionary]; + } - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) - { - GCDAsyncReadPacket *theRead = currentRead; + GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, + ^{ @autoreleasepool { - NSTimeInterval timeoutExtension = 0.0; + if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites)) + { + [self->readQueue addObject:packet]; + [self->writeQueue addObject:packet]; - timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag - elapsed:theRead->timeout - bytesDone:theRead->bytesDone]; + self->flags |= kQueuedTLS; - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } + }}); - [self doReadTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doReadTimeoutWithExtension:0.0]; - } } -- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension +- (void)maybeStartTLS { - if (currentRead) - { - if (timeoutExtension > 0.0) - { - currentRead->timeout += timeoutExtension; + // We can't start TLS until: + // - All queued reads prior to the user calling startTLS are complete + // - All queued writes prior to the user calling startTLS are complete + // + // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0); + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + BOOL useSecureTransport = YES; - // Unpause reads, and continue - flags &= ~kReadsPaused; - [self doReadData]; - } - else - { - LogVerbose(@"ReadTimeout"); +#if TARGET_OS_IPHONE + { + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + NSDictionary *tlsSettings = @{}; + if (tlsPacket) { + tlsSettings = tlsPacket->tlsSettings; + } + NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; + if (value && [value boolValue]) + useSecureTransport = NO; + } +#endif - [self closeWithError:[self readTimeoutError]]; - } - } + if (useSecureTransport) + { + [self ssl_startTLS]; + } + else + { +#if TARGET_OS_IPHONE + [self cf_startTLS]; +#endif + } + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Writing +#pragma mark Security via SecureTransport //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag +- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength { - if ([data length] == 0) return; + LogVerbose(@"sslReadWithBuffer:%p length:%lu", + buffer, + (unsigned long)*bufferLength); - GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; + if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) + { + LogVerbose(@"%@ - No data available to read...", THIS_METHOD); - dispatch_async(socketQueue, ^{ @autoreleasepool { + // No data available to read. + // + // Need to wait for readSource to fire and notify us of + // available data in the socket's internal read buffer. - LogTrace(); + [self resumeReadSource]; - if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites)) - { - [self->writeQueue addObject:packet]; - [self maybeDequeueWrite]; - } - }}); + *bufferLength = 0; + return errSSLWouldBlock; + } - // Do not rely on the block being run in order to release the packet, - // as the queue might get released without the block completing. -} + size_t totalBytesRead = 0; + size_t totalBytesLeftToBeRead = *bufferLength; -- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr -{ - __block float result = 0.0F; + BOOL done = NO; + BOOL socketError = NO; - dispatch_block_t block = ^{ + // + // STEP 1 : READ FROM SSL PRE BUFFER + // - if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]]) - { - // We're not writing anything right now. + size_t sslPreBufferLength = [sslPreBuffer availableBytes]; - if (tagPtr != NULL) *tagPtr = 0; - if (donePtr != NULL) *donePtr = 0; - if (totalPtr != NULL) *totalPtr = 0; + if (sslPreBufferLength > 0) + { + LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); - result = NAN; - } - else - { - NSUInteger done = self->currentWrite->bytesDone; - NSUInteger total = [self->currentWrite->buffer length]; + size_t bytesToCopy; + if (sslPreBufferLength > totalBytesLeftToBeRead) + bytesToCopy = totalBytesLeftToBeRead; + else + bytesToCopy = sslPreBufferLength; - if (tagPtr != NULL) *tagPtr = self->currentWrite->tag; - if (donePtr != NULL) *donePtr = done; - if (totalPtr != NULL) *totalPtr = total; + LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", + THIS_METHOD, + bytesToCopy); - result = (float)done / (float)total; - } - }; + memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); + [sslPreBuffer didRead:bytesToCopy]; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + LogVerbose(@"%@: sslPreBuffer.length = %zu", + THIS_METHOD, + [sslPreBuffer availableBytes]); - return result; -} + totalBytesRead += bytesToCopy; + totalBytesLeftToBeRead -= bytesToCopy; -/** - * Conditionally starts a new write. - * - * It is called when: - * - a user requests a write - * - after a write request has finished (to handle the next request) - * - immediately after the socket opens to handle any pending requests - * - * This method also handles auto-disconnect post read/write completion. -**/ -- (void)maybeDequeueWrite -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - - // If we're not currently processing a write AND we have an available write stream - if ((currentWrite == nil) && (flags & kConnected)) - { - if ([writeQueue count] > 0) - { - // Dequeue the next object in the write queue - currentWrite = [writeQueue objectAtIndex:0]; - [writeQueue removeObjectAtIndex:0]; - - - if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]]) - { - LogVerbose(@"Dequeued GCDAsyncSpecialPacket"); - - // Attempt to start TLS - flags |= kStartingWriteTLS; - - // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set - [self maybeStartTLS]; - } - else - { - LogVerbose(@"Dequeued GCDAsyncWritePacket"); - - // Setup write timer (if needed) - [self setupWriteTimerWithTimeout:currentWrite->timeout]; - - // Immediately write, if possible - [self doWriteData]; - } - } - else if (flags & kDisconnectAfterWrites) - { - if (flags & kDisconnectAfterReads) - { - if (([readQueue count] == 0) && (currentRead == nil)) - { - [self closeWithError:nil]; - } - } - else - { - [self closeWithError:nil]; - } - } - } -} + done = (totalBytesLeftToBeRead == 0); -- (void)doWriteData -{ - LogTrace(); + if (done) LogVerbose(@"%@: Complete", THIS_METHOD); + } - // This method is called by the writeSource via the socketQueue + // + // STEP 2 : READ FROM SOCKET + // - if ((currentWrite == nil) || (flags & kWritesPaused)) - { - LogVerbose(@"No currentWrite or kWritesPaused"); - - // Unable to write at this time - - if ([self usingCFStreamForTLS]) - { - // CFWriteStream only fires once when there is available data. - // It won't fire again until we've invoked CFWriteStreamWrite. - } - else - { - // If the writeSource is firing, we need to pause it - // or else it will continue to fire over and over again. - - if (flags & kSocketCanAcceptBytes) - { - [self suspendWriteSource]; - } - } - return; - } - - if (!(flags & kSocketCanAcceptBytes)) - { - LogVerbose(@"No space available to write..."); - - // No space available to write. - - if (![self usingCFStreamForTLS]) - { - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. - - [self resumeWriteSource]; - } - return; - } - - if (flags & kStartingWriteTLS) - { - LogVerbose(@"Waiting for SSL/TLS handshake to complete"); - - // The writeQueue is waiting for SSL/TLS handshake to complete. - - if (flags & kStartingReadTLS) - { - if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock) - { - // We are in the process of a SSL Handshake. - // We were waiting for available space in the socket's internal OS buffer to continue writing. - - [self ssl_continueSSLHandshake]; - } - } - else - { - // We are still waiting for the readQueue to drain and start the SSL/TLS process. - // We now know we can write to the socket. - - if (![self usingCFStreamForTLS]) - { - // Suspend the write source or else it will continue to fire nonstop. - - [self suspendWriteSource]; - } - } - - return; - } - - // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet) - - BOOL waiting = NO; - NSError *error = nil; - size_t bytesWritten = 0; - - if (flags & kSocketSecure) - { - if ([self usingCFStreamForTLS]) - { - #if TARGET_OS_IPHONE - - // - // Writing data using CFStream (over internal TLS) - // - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite); - LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result); - - if (result < 0) - { - error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream); - } - else - { - bytesWritten = (size_t)result; - - // We always set waiting to true in this scenario. - // CFStream may have altered our underlying socket to non-blocking. - // Thus if we attempt to write without a callback, we may end up blocking our queue. - waiting = YES; - } - - #endif - } - else - { - // We're going to use the SSLWrite function. - // - // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed) - // - // Parameters: - // context - An SSL session context reference. - // data - A pointer to the buffer of data to write. - // dataLength - The amount, in bytes, of data to write. - // processed - On return, the length, in bytes, of the data actually written. - // - // It sounds pretty straight-forward, - // but there are a few caveats you should be aware of. - // - // The SSLWrite method operates in a non-obvious (and rather annoying) manner. - // According to the documentation: - // - // Because you may configure the underlying connection to operate in a non-blocking manner, - // a write operation might return errSSLWouldBlock, indicating that less data than requested - // was actually transferred. In this case, you should repeat the call to SSLWrite until some - // other result is returned. - // - // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock, - // then the SSLWrite method returns (with the proper errSSLWouldBlock return value), - // but it sets processed to dataLength !! - // - // In other words, if the SSLWrite function doesn't completely write all the data we tell it to, - // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to - // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written. - // - // You might be wondering: - // If the SSLWrite function doesn't tell us how many bytes were written, - // then how in the world are we supposed to update our parameters (buffer & bytesToWrite) - // for the next time we invoke SSLWrite? - // - // The answer is that SSLWrite cached all the data we told it to write, - // and it will push out that data next time we call SSLWrite. - // If we call SSLWrite with new data, it will push out the cached data first, and then the new data. - // If we call SSLWrite with empty data, then it will simply push out the cached data. - // - // For this purpose we're going to break large writes into a series of smaller writes. - // This allows us to report progress back to the delegate. - - OSStatus result; - - BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0); - BOOL hasNewDataToWrite = YES; - - if (hasCachedDataToWrite) - { - size_t processed = 0; - - result = SSLWrite(sslContext, NULL, 0, &processed); - - if (result == noErr) - { - bytesWritten = sslWriteCachedLength; - sslWriteCachedLength = 0; - - if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten)) - { - // We've written all data for the current write. - hasNewDataToWrite = NO; - } - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - } - else - { - error = [self sslError:result]; - } - - // Can't write any new data since we were unable to write the cached data. - hasNewDataToWrite = NO; - } - } - - if (hasNewDataToWrite) - { - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] - + currentWrite->bytesDone - + bytesWritten; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - size_t bytesRemaining = bytesToWrite; - - BOOL keepLooping = YES; - while (keepLooping) - { - const size_t sslMaxBytesToWrite = 32768; - size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite); - size_t sslBytesWritten = 0; - - result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten); - - if (result == noErr) - { - buffer += sslBytesWritten; - bytesWritten += sslBytesWritten; - bytesRemaining -= sslBytesWritten; - - keepLooping = (bytesRemaining > 0); - } - else - { - if (result == errSSLWouldBlock) - { - waiting = YES; - sslWriteCachedLength = sslBytesToWrite; - } - else - { - error = [self sslError:result]; - } - - keepLooping = NO; - } - - } // while (keepLooping) - - } // if (hasNewDataToWrite) - } - } - else - { - // - // Writing data directly over raw socket - // - - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - - const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone; - - NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone; - - if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3) - { - bytesToWrite = SIZE_MAX; - } - - ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite); - LogVerbose(@"wrote to socket = %zd", result); - - // Check results - if (result < 0) - { - if (errno == EWOULDBLOCK) - { - waiting = YES; - } - else - { - error = [self errorWithErrno:errno reason:@"Error in write() function"]; - } - } - else - { - bytesWritten = result; - } - } - - // We're done with our writing. - // If we explictly ran into a situation where the socket told us there was no room in the buffer, - // then we immediately resume listening for notifications. - // - // We must do this before we dequeue another write, - // as that may in turn invoke this method again. - // - // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode. - - if (waiting) - { - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - // Check our results - - BOOL done = NO; - - if (bytesWritten > 0) - { - // Update total amount read for the current write - currentWrite->bytesDone += bytesWritten; - LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone); - - // Is packet done? - done = (currentWrite->bytesDone == [currentWrite->buffer length]); - } - - if (done) - { - [self completeCurrentWrite]; - - if (!error) - { - dispatch_async(socketQueue, ^{ @autoreleasepool{ - - [self maybeDequeueWrite]; - }}); - } - } - else - { - // We were unable to finish writing the data, - // so we're waiting for another callback to notify us of available space in the lower-level output buffer. - - if (!waiting && !error) - { - // This would be the case if our write was able to accept some data, but not all of it. - - flags &= ~kSocketCanAcceptBytes; - - if (![self usingCFStreamForTLS]) - { - [self resumeWriteSource]; - } - } - - if (bytesWritten > 0) - { - // We're not done with the entire write, but we have written some bytes - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)]) - { - long theWriteTag = currentWrite->tag; - - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag]; - }}); - } - } - } - - // Check for errors - - if (error) - { - [self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]]; - } - - // Do not add any code here without first adding a return statement in the error case above. -} + if (!done && (socketFDBytesAvailable > 0)) + { + LogVerbose(@"%@: Reading from socket...", THIS_METHOD); -- (void)completeCurrentWrite -{ - LogTrace(); + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - NSAssert(currentWrite, @"Trying to complete current write when there is no current write."); + BOOL readIntoPreBuffer; + size_t bytesToRead; + uint8_t *buf; + if (socketFDBytesAvailable > totalBytesLeftToBeRead) + { + // Read all available data from socket into sslPreBuffer. + // Then copy requested amount into dataBuffer. - __strong id theDelegate = delegate; + LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)]) - { - long theWriteTag = currentWrite->tag; + [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + readIntoPreBuffer = YES; + bytesToRead = (size_t)socketFDBytesAvailable; + buf = [sslPreBuffer writeBuffer]; + } + else + { + // Read available data from socket directly into dataBuffer. - [theDelegate socket:self didWriteDataWithTag:theWriteTag]; - }}); - } + LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); - [self endCurrentWrite]; -} + readIntoPreBuffer = NO; + bytesToRead = totalBytesLeftToBeRead; + buf = (uint8_t *)buffer + totalBytesRead; + } -- (void)endCurrentWrite -{ - if (writeTimer) - { - dispatch_source_cancel(writeTimer); - writeTimer = NULL; - } + ssize_t result = read(socketFD, buf, bytesToRead); + LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); - currentWrite = nil; -} + if (result < 0) + { + LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); -- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout -{ - if (timeout >= 0.0) - { - writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + if (errno != EWOULDBLOCK) + { + socketError = YES; + } + + socketFDBytesAvailable = 0; + } + else if (result == 0) + { + LogVerbose(@"%@: read EOF", THIS_METHOD); + + socketError = YES; + socketFDBytesAvailable = 0; + } + else + { + size_t bytesReadFromSocket = result; + + if (socketFDBytesAvailable > bytesReadFromSocket) + socketFDBytesAvailable -= bytesReadFromSocket; + else + socketFDBytesAvailable = 0; + + if (readIntoPreBuffer) + { + [sslPreBuffer didWrite:bytesReadFromSocket]; - __weak GCDAsyncSocket *weakSelf = self; + size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); - dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", + THIS_METHOD, + bytesToCopy); - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf == nil) return_from_block; + memcpy((uint8_t *)buffer + totalBytesRead, + [sslPreBuffer readBuffer], + bytesToCopy); + [sslPreBuffer didRead:bytesToCopy]; - [strongSelf doWriteTimeout]; + totalBytesRead += bytesToCopy; + totalBytesLeftToBeRead -= bytesToCopy; - #pragma clang diagnostic pop - }}); + LogVerbose(@"%@: sslPreBuffer.length = %zu", + THIS_METHOD, + [sslPreBuffer availableBytes]); + } + else + { + totalBytesRead += bytesReadFromSocket; + totalBytesLeftToBeRead -= bytesReadFromSocket; + } - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theWriteTimer = writeTimer; - dispatch_source_set_cancel_handler(writeTimer, ^{ - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + done = (totalBytesLeftToBeRead == 0); - LogVerbose(@"dispatch_release(writeTimer)"); - dispatch_release(theWriteTimer); + if (done) LogVerbose(@"%@: Complete", THIS_METHOD); + } + } - #pragma clang diagnostic pop - }); - #endif + *bufferLength = totalBytesRead; - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + if (done) + return noErr; - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(writeTimer); - } + if (socketError) + return errSSLClosedAbort; + + return errSSLWouldBlock; } -- (void)doWriteTimeout +- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength { - // This is a little bit tricky. - // Ideally we'd like to synchronously query the delegate about a timeout extension. - // But if we do so synchronously we risk a possible deadlock. - // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block. + if (!(flags & kSocketCanAcceptBytes)) + { + // Unable to write. + // + // Need to wait for writeSource to fire and notify us of + // available space in the socket's internal write buffer. + + [self resumeWriteSource]; + + *bufferLength = 0; + return errSSLWouldBlock; + } - flags |= kWritesPaused; + size_t bytesToWrite = *bufferLength; + size_t bytesWritten = 0; - __strong id theDelegate = delegate; + BOOL done = NO; + BOOL socketError = NO; - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) - { - GCDAsyncWritePacket *theWrite = currentWrite; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - dispatch_async(delegateQueue, ^{ @autoreleasepool { + ssize_t result = write(socketFD, buffer, bytesToWrite); + + if (result < 0) + { + if (errno != EWOULDBLOCK) + { + socketError = YES; + } + + flags &= ~kSocketCanAcceptBytes; + } + else if (result == 0) + { + flags &= ~kSocketCanAcceptBytes; + } + else + { + bytesWritten = result; + + done = (bytesWritten == bytesToWrite); + } - NSTimeInterval timeoutExtension = 0.0; + *bufferLength = bytesWritten; - timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag - elapsed:theWrite->timeout - bytesDone:theWrite->bytesDone]; + if (done) + return noErr; - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + if (socketError) + return errSSLClosedAbort; - [self doWriteTimeoutWithExtension:timeoutExtension]; - }}); - }}); - } - else - { - [self doWriteTimeoutWithExtension:0.0]; - } + return errSSLWouldBlock; } -- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension +static OSStatus SSLReadFunction(SSLConnectionRef connection, + void *data, + size_t *dataLength) { - if (currentWrite) - { - if (timeoutExtension > 0.0) - { - currentWrite->timeout += timeoutExtension; - - // Reschedule the timer - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC)); - dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0); + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; - // Unpause writes, and continue - flags &= ~kWritesPaused; - [self doWriteData]; - } - else - { - LogVerbose(@"WriteTimeout"); + NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), + @"What the deuce?"); - [self closeWithError:[self writeTimeoutError]]; - } - } + return [asyncSocket sslReadWithBuffer:data length:dataLength]; } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static OSStatus SSLWriteFunction(SSLConnectionRef connection, + const void *data, + size_t *dataLength) +{ + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; -- (void)startTLS:(NSDictionary *)tlsSettings + NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), + @"What the deuce?"); + + return [asyncSocket sslWriteWithBuffer:data length:dataLength]; +} + +- (void)ssl_startTLS { - LogTrace(); + LogTrace(); - if (tlsSettings == nil) - { - // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, - // but causes problems if we later try to fetch the remote host's certificate. - // - // To be exact, it causes the following to return NULL instead of the normal result: - // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) - // - // So we use an empty dictionary instead, which works perfectly. + LogVerbose(@"Starting TLS (via SecureTransport)..."); - tlsSettings = [NSDictionary dictionary]; - } + OSStatus status; - GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + if (tlsPacket == nil) // Code to quiet the analyzer + { + NSAssert(NO, @"Logic error"); - dispatch_async(socketQueue, ^{ @autoreleasepool { + [self closeWithError:[self otherError:@"Logic error"]]; + return; + } + NSDictionary *tlsSettings = tlsPacket->tlsSettings; - if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites)) - { - [self->readQueue addObject:packet]; - [self->writeQueue addObject:packet]; + // Create SSLContext, and setup IO callbacks and connection ref - self->flags |= kQueuedTLS; + NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer]; + BOOL isServer = [isServerNumber boolValue]; - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - }}); +#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) + { + if (isServer) + sslContext = SSLCreateContext(kCFAllocatorDefault, + kSSLServerSide, + kSSLStreamType); + else + sslContext = SSLCreateContext(kCFAllocatorDefault, + kSSLClientSide, + kSSLStreamType); -} + if (sslContext == NULL) + { + [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; + return; + } + } +#else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) + { + status = SSLNewContext(isServer, &sslContext); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; + return; + } + } +#endif -- (void)maybeStartTLS -{ - // We can't start TLS until: - // - All queued reads prior to the user calling startTLS are complete - // - All queued writes prior to the user calling startTLS are complete - // - // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set - - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - BOOL useSecureTransport = YES; - - #if TARGET_OS_IPHONE - { - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - NSDictionary *tlsSettings = @{}; - if (tlsPacket) { - tlsSettings = tlsPacket->tlsSettings; - } - NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS]; - if (value && [value boolValue]) - useSecureTransport = NO; - } - #endif - - if (useSecureTransport) - { - [self ssl_startTLS]; - } - else - { - #if TARGET_OS_IPHONE - [self cf_startTLS]; - #endif - } - } -} + status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; + return; + } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#pragma mark Security via SecureTransport -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; + return; + } -- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength -{ - LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength); - if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0)) - { - LogVerbose(@"%@ - No data available to read...", THIS_METHOD); + NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust]; + if ([shouldManuallyEvaluateTrust boolValue]) + { + if (isServer) + { + [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; + return; + } + + status = SSLSetSessionOption(sslContext, + kSSLSessionOptionBreakOnServerAuth, + true); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; + return; + } + +#if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - // No data available to read. - // - // Need to wait for readSource to fire and notify us of - // available data in the socket's internal read buffer. + // Note from Apple's documentation: + // + // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. + // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the + // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus + // SSLSetEnableCertVerify is not available on that platform at all. + + status = SSLSetEnableCertVerify(sslContext, NO); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; + return; + } - [self resumeReadSource]; +#endif + } - *bufferLength = 0; - return errSSLWouldBlock; - } + // Configure SSLContext from given settings + // + // Checklist: + // 1. kCFStreamSSLPeerName + // 2. kCFStreamSSLCertificates + // 3. GCDAsyncSocketSSLPeerID + // 4. GCDAsyncSocketSSLProtocolVersionMin + // 5. GCDAsyncSocketSSLProtocolVersionMax + // 6. GCDAsyncSocketSSLSessionOptionFalseStart + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord + // 8. GCDAsyncSocketSSLCipherSuites + // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) + // 10. GCDAsyncSocketSSLALPN + // + // Deprecated (throw error): + // 10. kCFStreamSSLAllowsAnyRoot + // 11. kCFStreamSSLAllowsExpiredRoots + // 12. kCFStreamSSLAllowsExpiredCertificates + // 13. kCFStreamSSLValidatesCertificateChain + // 14. kCFStreamSSLLevel + + NSObject *value; + + // 1. kCFStreamSSLPeerName + + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName]; + if ([value isKindOfClass:[NSString class]]) + { + NSString *peerName = (NSString *)value; + + const char *peer = [peerName UTF8String]; + size_t peerLen = strlen(peer); + + status = SSLSetPeerDomainName(sslContext, peer, peerLen); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; + return; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - size_t totalBytesRead = 0; - size_t totalBytesLeftToBeRead = *bufferLength; + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; + return; + } - BOOL done = NO; - BOOL socketError = NO; + // 2. kCFStreamSSLCertificates - // - // STEP 1 : READ FROM SSL PRE BUFFER - // + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; + if ([value isKindOfClass:[NSArray class]]) + { + NSArray *certs = (NSArray *)value; - size_t sslPreBufferLength = [sslPreBuffer availableBytes]; + status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; + return; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - if (sslPreBufferLength > 0) - { - LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD); + [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; + return; + } - size_t bytesToCopy; - if (sslPreBufferLength > totalBytesLeftToBeRead) - bytesToCopy = totalBytesLeftToBeRead; - else - bytesToCopy = sslPreBufferLength; + // 3. GCDAsyncSocketSSLPeerID - LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy); + value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; + if ([value isKindOfClass:[NSData class]]) + { + NSData *peerIdData = (NSData *)value; - memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; + status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." + @" (You can convert strings to data using a method like" + @" [string dataUsingEncoding:NSUTF8StringEncoding])"); + + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; + return; + } - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); + // 4. GCDAsyncSocketSSLProtocolVersionMin - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (minProtocol != kSSLProtocolUnknown) + { + status = SSLSetProtocolVersionMin(sslContext, minProtocol); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; + return; + } + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); - done = (totalBytesLeftToBeRead == 0); + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; + return; + } - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } + // 5. GCDAsyncSocketSSLProtocolVersionMax - // - // STEP 2 : READ FROM SOCKET - // + value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; + if ([value isKindOfClass:[NSNumber class]]) + { + SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; + if (maxProtocol != kSSLProtocolUnknown) + { + status = SSLSetProtocolVersionMax(sslContext, maxProtocol); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; + return; + } + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); - if (!done && (socketFDBytesAvailable > 0)) - { - LogVerbose(@"%@: Reading from socket...", THIS_METHOD); + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; + return; + } - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + // 6. GCDAsyncSocketSSLSessionOptionFalseStart - BOOL readIntoPreBuffer; - size_t bytesToRead; - uint8_t *buf; + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; + if ([value isKindOfClass:[NSNumber class]]) + { + NSNumber *falseStart = (NSNumber *)value; + status = SSLSetSessionOption(sslContext, + kSSLSessionOptionFalseStart, + [falseStart boolValue]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); - if (socketFDBytesAvailable > totalBytesLeftToBeRead) - { - // Read all available data from socket into sslPreBuffer. - // Then copy requested amount into dataBuffer. + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; + return; + } - LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD); + // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable]; + value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; + if ([value isKindOfClass:[NSNumber class]]) + { + NSNumber *oneByteRecord = (NSNumber *)value; + status = SSLSetSessionOption(sslContext, + kSSLSessionOptionSendOneByteRecord, + [oneByteRecord boolValue]); + if (status != noErr) + { + [self closeWithError: + [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; + return; + } + } + else if (value) + { + NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." + @" Value must be of type NSNumber."); - readIntoPreBuffer = YES; - bytesToRead = (size_t)socketFDBytesAvailable; - buf = [sslPreBuffer writeBuffer]; - } - else - { - // Read available data from socket directly into dataBuffer. + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; + return; + } - LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD); + // 8. GCDAsyncSocketSSLCipherSuites - readIntoPreBuffer = NO; - bytesToRead = totalBytesLeftToBeRead; - buf = (uint8_t *)buffer + totalBytesRead; - } + value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; + if ([value isKindOfClass:[NSArray class]]) + { + NSArray *cipherSuites = (NSArray *)value; + NSUInteger numberCiphers = [cipherSuites count]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvla" + SSLCipherSuite ciphers[numberCiphers]; +#pragma clang diagnostic pop - ssize_t result = read(socketFD, buf, bytesToRead); - LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result); + NSUInteger cipherIndex; + for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) + { + NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; + ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue]; + } - if (result < 0) - { - LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno); + status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; + return; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); - if (errno != EWOULDBLOCK) - { - socketError = YES; - } + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; + return; + } - socketFDBytesAvailable = 0; - } - else if (result == 0) - { - LogVerbose(@"%@: read EOF", THIS_METHOD); + // 9. GCDAsyncSocketSSLDiffieHellmanParameters - socketError = YES; - socketFDBytesAvailable = 0; - } - else - { - size_t bytesReadFromSocket = result; +#if !TARGET_OS_IPHONE + value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; + if ([value isKindOfClass:[NSData class]]) + { + NSData *diffieHellmanData = (NSData *)value; + + status = SSLSetDiffieHellmanParams(sslContext, + [diffieHellmanData bytes], + [diffieHellmanData length]); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; + return; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); - if (socketFDBytesAvailable > bytesReadFromSocket) - socketFDBytesAvailable -= bytesReadFromSocket; - else - socketFDBytesAvailable = 0; + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; + return; + } +#endif - if (readIntoPreBuffer) - { - [sslPreBuffer didWrite:bytesReadFromSocket]; + // 10. kCFStreamSSLCertificates + value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN]; + if ([value isKindOfClass:[NSArray class]]) + { + if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) + { + CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value); + status = SSLSetALPNProtocols(sslContext, protocols); + if (status != noErr) + { + [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]]; + return; + } + } + else + { + NSAssert(NO, @"Security option unavailable - GCDAsyncSocketSSLALPN" + @" - iOS 11.0, macOS 10.13 required"); + [self closeWithError:[self otherError:@"Security option unavailable - GCDAsyncSocketSSLALPN"]]; + } + } + else if (value) + { + NSAssert(NO, + @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray."); - size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket); + [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]]; + return; + } - LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy); + // DEPRECATED checks - memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy); - [sslPreBuffer didRead:bytesToCopy]; + // 10. kCFStreamSSLAllowsAnyRoot - totalBytesRead += bytesToCopy; - totalBytesLeftToBeRead -= bytesToCopy; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; +#pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" + @" - You must use manual trust evaluation"); - LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]); - } - else - { - totalBytesRead += bytesReadFromSocket; - totalBytesLeftToBeRead -= bytesReadFromSocket; - } + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; + return; + } - done = (totalBytesLeftToBeRead == 0); + // 11. kCFStreamSSLAllowsExpiredRoots - if (done) LogVerbose(@"%@: Complete", THIS_METHOD); - } - } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; +#pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" + @" - You must use manual trust evaluation"); - *bufferLength = totalBytesRead; + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; + return; + } - if (done) - return noErr; + // 12. kCFStreamSSLValidatesCertificateChain - if (socketError) - return errSSLClosedAbort; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; +#pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" + @" - You must use manual trust evaluation"); - return errSSLWouldBlock; -} + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; + return; + } -- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength -{ - if (!(flags & kSocketCanAcceptBytes)) - { - // Unable to write. - // - // Need to wait for writeSource to fire and notify us of - // available space in the socket's internal write buffer. + // 13. kCFStreamSSLAllowsExpiredCertificates - [self resumeWriteSource]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; +#pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" + @" - You must use manual trust evaluation"); - *bufferLength = 0; - return errSSLWouldBlock; - } + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; + return; + } - size_t bytesToWrite = *bufferLength; - size_t bytesWritten = 0; + // 14. kCFStreamSSLLevel - BOOL done = NO; - BOOL socketError = NO; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; +#pragma clang diagnostic pop + if (value) + { + NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" + @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; + return; + } - ssize_t result = write(socketFD, buffer, bytesToWrite); + // Setup the sslPreBuffer + // + // Any data in the preBuffer needs to be moved into the sslPreBuffer, + // as this data is now part of the secure read stream. - if (result < 0) - { - if (errno != EWOULDBLOCK) - { - socketError = YES; - } + sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; - flags &= ~kSocketCanAcceptBytes; - } - else if (result == 0) - { - flags &= ~kSocketCanAcceptBytes; - } - else - { - bytesWritten = result; + size_t preBufferLength = [preBuffer availableBytes]; - done = (bytesWritten == bytesToWrite); - } + if (preBufferLength > 0) + { + [sslPreBuffer ensureCapacityForWrite:preBufferLength]; - *bufferLength = bytesWritten; + memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); + [preBuffer didRead:preBufferLength]; + [sslPreBuffer didWrite:preBufferLength]; + } - if (done) - return noErr; + sslErrCode = lastSSLHandshakeError = noErr; - if (socketError) - return errSSLClosedAbort; + // Start the SSL Handshake process - return errSSLWouldBlock; + [self ssl_continueSSLHandshake]; } -static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength) +- (void)ssl_continueSSLHandshake { - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; + LogTrace(); - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); + // If the return value is noErr, the session is ready for normal secure communication. + // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. + // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the + // server and then call SSLHandshake again to resume the handshake or close the connection + // errSSLPeerBadCert SSL error. + // Otherwise, the return value indicates an error code. - return [asyncSocket sslReadWithBuffer:data length:dataLength]; -} + OSStatus status = SSLHandshake(sslContext); + lastSSLHandshakeError = status; -static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength) -{ - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection; + if (status == noErr) + { + LogVerbose(@"SSLHandshake complete"); - NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?"); + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; - return [asyncSocket sslWriteWithBuffer:data length:dataLength]; -} + flags |= kSocketSecure; -- (void)ssl_startTLS -{ - LogTrace(); - - LogVerbose(@"Starting TLS (via SecureTransport)..."); - - OSStatus status; - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - if (tlsPacket == nil) // Code to quiet the analyzer - { - NSAssert(NO, @"Logic error"); - - [self closeWithError:[self otherError:@"Logic error"]]; - return; - } - NSDictionary *tlsSettings = tlsPacket->tlsSettings; - - // Create SSLContext, and setup IO callbacks and connection ref - - NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer]; - BOOL isServer = [isServerNumber boolValue]; - - #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) - { - if (isServer) - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); - else - sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); - - if (sslContext == NULL) - { - [self closeWithError:[self otherError:@"Error in SSLCreateContext"]]; - return; - } - } - #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - { - status = SSLNewContext(isServer, &sslContext); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLNewContext"]]; - return; - } - } - #endif - - status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]]; - return; - } - - status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetConnection"]]; - return; - } - - - NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust]; - if ([shouldManuallyEvaluateTrust boolValue]) - { - if (isServer) - { - [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]]; - return; - } - - status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]]; - return; - } - - #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080) - - // Note from Apple's documentation: - // - // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8. - // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the - // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus - // SSLSetEnableCertVerify is not available on that platform at all. - - status = SSLSetEnableCertVerify(sslContext, NO); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]]; - return; - } - - #endif - } - - // Configure SSLContext from given settings - // - // Checklist: - // 1. kCFStreamSSLPeerName - // 2. kCFStreamSSLCertificates - // 3. GCDAsyncSocketSSLPeerID - // 4. GCDAsyncSocketSSLProtocolVersionMin - // 5. GCDAsyncSocketSSLProtocolVersionMax - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - // 8. GCDAsyncSocketSSLCipherSuites - // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac) - // 10. GCDAsyncSocketSSLALPN - // - // Deprecated (throw error): - // 10. kCFStreamSSLAllowsAnyRoot - // 11. kCFStreamSSLAllowsExpiredRoots - // 12. kCFStreamSSLAllowsExpiredCertificates - // 13. kCFStreamSSLValidatesCertificateChain - // 14. kCFStreamSSLLevel - - NSObject *value; - - // 1. kCFStreamSSLPeerName - - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName]; - if ([value isKindOfClass:[NSString class]]) - { - NSString *peerName = (NSString *)value; - - const char *peer = [peerName UTF8String]; - size_t peerLen = strlen(peer); - - status = SSLSetPeerDomainName(sslContext, peer, peerLen); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]]; - return; - } - - // 2. kCFStreamSSLCertificates - - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates]; - if ([value isKindOfClass:[NSArray class]]) - { - NSArray *certs = (NSArray *)value; - - status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]]; - return; - } - - // 3. GCDAsyncSocketSSLPeerID - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *peerIdData = (NSData *)value; - - status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData." - @" (You can convert strings to data using a method like" - @" [string dataUsingEncoding:NSUTF8StringEncoding])"); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]]; - return; - } - - // 4. GCDAsyncSocketSSLProtocolVersionMin - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (minProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMin(sslContext, minProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]]; - return; - } - - // 5. GCDAsyncSocketSSLProtocolVersionMax - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax]; - if ([value isKindOfClass:[NSNumber class]]) - { - SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue]; - if (maxProtocol != kSSLProtocolUnknown) - { - status = SSLSetProtocolVersionMax(sslContext, maxProtocol); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]]; - return; - } - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]]; - return; - } - - // 6. GCDAsyncSocketSSLSessionOptionFalseStart - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart]; - if ([value isKindOfClass:[NSNumber class]]) - { - NSNumber *falseStart = (NSNumber *)value; - status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [falseStart boolValue]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]]; - return; - } - - // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord]; - if ([value isKindOfClass:[NSNumber class]]) - { - NSNumber *oneByteRecord = (NSNumber *)value; - status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [oneByteRecord boolValue]); - if (status != noErr) - { - [self closeWithError: - [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord." - @" Value must be of type NSNumber."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]]; - return; - } - - // 8. GCDAsyncSocketSSLCipherSuites - - value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites]; - if ([value isKindOfClass:[NSArray class]]) - { - NSArray *cipherSuites = (NSArray *)value; - NSUInteger numberCiphers = [cipherSuites count]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wvla" - SSLCipherSuite ciphers[numberCiphers]; -#pragma clang diagnostic pop + __strong id theDelegate = delegate; - NSUInteger cipherIndex; - for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++) - { - NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex]; - ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue]; - } - - status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]]; - return; - } - - // 9. GCDAsyncSocketSSLDiffieHellmanParameters - - #if !TARGET_OS_IPHONE - value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters]; - if ([value isKindOfClass:[NSData class]]) - { - NSData *diffieHellmanData = (NSData *)value; - - status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]]; - return; - } - } - else if (value) - { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData."); - - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]]; - return; - } - #endif - - // 10. kCFStreamSSLCertificates - value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN]; - if ([value isKindOfClass:[NSArray class]]) - { - if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) - { - CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value); - status = SSLSetALPNProtocols(sslContext, protocols); - if (status != noErr) - { - [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]]; - return; - } - } - else - { - NSAssert(NO, @"Security option unavailable - GCDAsyncSocketSSLALPN" - @" - iOS 11.0, macOS 10.13 required"); - [self closeWithError:[self otherError:@"Security option unavailable - GCDAsyncSocketSSLALPN"]]; - } - } - else if (value) + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) { - NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray."); + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]]; - return; + [theDelegate socketDidSecure:self]; + }}); } - // DEPRECATED checks - - // 10. kCFStreamSSLAllowsAnyRoot - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]]; - return; - } - - // 11. kCFStreamSSLAllowsExpiredRoots - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]]; - return; - } + [self endCurrentRead]; + [self endCurrentWrite]; - // 12. kCFStreamSSLValidatesCertificateChain - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain" - @" - You must use manual trust evaluation"); - - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]]; - return; - } - - // 13. kCFStreamSSLAllowsExpiredCertificates - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates" - @" - You must use manual trust evaluation"); + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } + else if (status == errSSLPeerAuthCompleted) + { + LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]]; - return; - } + __block SecTrustRef trust = NULL; + status = SSLCopyPeerTrust(sslContext, &trust); + if (status != noErr) + { + [self closeWithError:[self sslError:status]]; + return; + } - // 14. kCFStreamSSLLevel + int aStateIndex = stateIndex; + dispatch_queue_t theSocketQueue = socketQueue; - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel]; - #pragma clang diagnostic pop - if (value) - { - NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel" - @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax"); + __weak GCDAsyncSocket *weakSelf = self; - [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]]; - return; - } + void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - // Setup the sslPreBuffer - // - // Any data in the preBuffer needs to be moved into the sslPreBuffer, - // as this data is now part of the secure read stream. + dispatch_async(theSocketQueue, ^{ @autoreleasepool { - sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)]; + if (trust) { + CFRelease(trust); + trust = NULL; + } - size_t preBufferLength = [preBuffer availableBytes]; + __strong GCDAsyncSocket *strongSelf = weakSelf; + if (strongSelf) + { + [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; + } + }}); - if (preBufferLength > 0) - { - [sslPreBuffer ensureCapacityForWrite:preBufferLength]; +#pragma clang diagnostic pop + }}; - memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength); - [preBuffer didRead:preBufferLength]; - [sslPreBuffer didWrite:preBufferLength]; - } + __strong id theDelegate = delegate; - sslErrCode = lastSSLHandshakeError = noErr; + if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) + { + dispatch_async(delegateQueue, + ^{ @autoreleasepool { - // Start the SSL Handshake process + [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; + }}); + } + else + { + if (trust) { + CFRelease(trust); + trust = NULL; + } - [self ssl_continueSSLHandshake]; -} + NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," + @" but delegate doesn't implement socket:shouldTrustPeer:"; -- (void)ssl_continueSSLHandshake -{ - LogTrace(); + [self closeWithError:[self otherError:msg]]; + return; + } + } + else if (status == errSSLWouldBlock) + { + LogVerbose(@"SSLHandshake continues..."); - // If the return value is noErr, the session is ready for normal secure communication. - // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again. - // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the - // server and then call SSLHandshake again to resume the handshake or close the connection - // errSSLPeerBadCert SSL error. - // Otherwise, the return value indicates an error code. - - OSStatus status = SSLHandshake(sslContext); - lastSSLHandshakeError = status; - - if (status == noErr) - { - LogVerbose(@"SSLHandshake complete"); - - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; - - flags |= kSocketSecure; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socketDidSecure:self]; - }}); - } - - [self endCurrentRead]; - [self endCurrentWrite]; - - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } - else if (status == errSSLPeerAuthCompleted) - { - LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval"); - - __block SecTrustRef trust = NULL; - status = SSLCopyPeerTrust(sslContext, &trust); - if (status != noErr) - { - [self closeWithError:[self sslError:status]]; - return; - } - - int aStateIndex = stateIndex; - dispatch_queue_t theSocketQueue = socketQueue; - - __weak GCDAsyncSocket *weakSelf = self; - - void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" - - dispatch_async(theSocketQueue, ^{ @autoreleasepool { - - if (trust) { - CFRelease(trust); - trust = NULL; - } - - __strong GCDAsyncSocket *strongSelf = weakSelf; - if (strongSelf) - { - [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex]; - } - }}); - - #pragma clang diagnostic pop - }}; - - __strong id theDelegate = delegate; - - if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { - - [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler]; - }}); - } - else - { - if (trust) { - CFRelease(trust); - trust = NULL; - } - - NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings," - @" but delegate doesn't implement socket:shouldTrustPeer:"; - - [self closeWithError:[self otherError:msg]]; - return; - } - } - else if (status == errSSLWouldBlock) - { - LogVerbose(@"SSLHandshake continues..."); - - // Handshake continues... - // - // This method will be called again from doReadData or doWriteData. - } - else - { - [self closeWithError:[self sslError:status]]; - } + // Handshake continues... + // + // This method will be called again from doReadData or doWriteData. + } + else + { + [self closeWithError:[self sslError:status]]; + } } - (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex { - LogTrace(); + LogTrace(); - if (aStateIndex != stateIndex) - { - LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); + if (aStateIndex != stateIndex) + { + LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)"); - // One of the following is true - // - the socket was disconnected - // - the startTLS operation timed out - // - the completionHandler was already invoked once + // One of the following is true + // - the socket was disconnected + // - the startTLS operation timed out + // - the completionHandler was already invoked once - return; - } + return; + } - // Increment stateIndex to ensure completionHandler can only be called once. - stateIndex++; + // Increment stateIndex to ensure completionHandler can only be called once. + stateIndex++; - if (shouldTrust) - { - NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError); - [self ssl_continueSSLHandshake]; - } - else - { - [self closeWithError:[self sslError:errSSLPeerBadCert]]; - } + if (shouldTrust) + { + NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, + @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", + (int)lastSSLHandshakeError); + [self ssl_continueSSLHandshake]; + } + else + { + [self closeWithError:[self sslError:errSSLPeerBadCert]]; + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -7470,131 +7667,137 @@ - (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex - (void)cf_finishSSLHandshake { - LogTrace(); + LogTrace(); - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; - flags |= kSocketSecure; + flags |= kSocketSecure; - __strong id theDelegate = delegate; + __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate socketDidSecure:self]; - }}); - } + [theDelegate socketDidSecure:self]; + }}); + } - [self endCurrentRead]; - [self endCurrentWrite]; + [self endCurrentRead]; + [self endCurrentWrite]; - [self maybeDequeueRead]; - [self maybeDequeueWrite]; - } + [self maybeDequeueRead]; + [self maybeDequeueWrite]; + } } - (void)cf_abortSSLHandshake:(NSError *)error { - LogTrace(); + LogTrace(); - if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) - { - flags &= ~kStartingReadTLS; - flags &= ~kStartingWriteTLS; + if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS)) + { + flags &= ~kStartingReadTLS; + flags &= ~kStartingWriteTLS; - [self closeWithError:error]; - } + [self closeWithError:error]; + } } - (void)cf_startTLS { - LogTrace(); - - LogVerbose(@"Starting TLS (via CFStream)..."); - - if ([preBuffer availableBytes] > 0) - { - NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; - - [self closeWithError:[self otherError:msg]]; - return; - } - - [self suspendReadSource]; - [self suspendWriteSource]; - - socketFDBytesAvailable = 0; - flags &= ~kSocketCanAcceptBytes; - flags &= ~kSecureSocketHasBytesAvailable; - - flags |= kUsingCFStreamForTLS; - - if (![self createReadAndWriteStream]) - { - [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; - return; - } - - if (![self registerForStreamCallbacksIncludingReadWrite:YES]) - { - [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; - return; - } - - if (![self addStreamsToRunLoop]) - { - [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; - return; - } - - NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS"); - NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS"); - - GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; - CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; - - // Getting an error concerning kCFStreamPropertySSLSettings ? - // CFNetwork/CFStream.h is imported for SSL constants needed by CFStream TLS support. - - BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings); - BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings); - - // For some reason, starting around the time of iOS 4.3, - // the first call to set the kCFStreamPropertySSLSettings will return true, - // but the second will return false. - // - // Order doesn't seem to matter. - // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. - // Either way, the first call will return true, and the second returns false. - // - // Interestingly, this doesn't seem to affect anything. - // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) - // setting it on one side of the stream automatically sets it for the other side of the stream. - // - // Although there isn't anything in the documentation to suggest that the second attempt would fail. - // - // Furthermore, this only seems to affect streams that are negotiating a security upgrade. - // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure - // connection, and then a startTLS is issued. - // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). - - if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. - { - [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; - return; - } - - if (![self openStreams]) - { - [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; - return; - } - - LogVerbose(@"Waiting for SSL Handshake to complete..."); + LogTrace(); + + LogVerbose(@"Starting TLS (via CFStream)..."); + + if ([preBuffer availableBytes] > 0) + { + NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket."; + + [self closeWithError:[self otherError:msg]]; + return; + } + + [self suspendReadSource]; + [self suspendWriteSource]; + + socketFDBytesAvailable = 0; + flags &= ~kSocketCanAcceptBytes; + flags &= ~kSecureSocketHasBytesAvailable; + + flags |= kUsingCFStreamForTLS; + + if (![self createReadAndWriteStream]) + { + [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]]; + return; + } + + if (![self registerForStreamCallbacksIncludingReadWrite:YES]) + { + [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]]; + return; + } + + if (![self addStreamsToRunLoop]) + { + [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]]; + return; + } + + NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], + @"Invalid read packet for startTLS"); + NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], + @"Invalid write packet for startTLS"); + + GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead; + CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings; + + // Getting an error concerning kCFStreamPropertySSLSettings ? + // CFNetwork/CFStream.h is imported for SSL constants needed by CFStream TLS support. + + BOOL r1 = CFReadStreamSetProperty(readStream, + kCFStreamPropertySSLSettings, + tlsSettings); + BOOL r2 = CFWriteStreamSetProperty(writeStream, + kCFStreamPropertySSLSettings, + tlsSettings); + + // For some reason, starting around the time of iOS 4.3, + // the first call to set the kCFStreamPropertySSLSettings will return true, + // but the second will return false. + // + // Order doesn't seem to matter. + // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order. + // Either way, the first call will return true, and the second returns false. + // + // Interestingly, this doesn't seem to affect anything. + // Which is not altogether unusual, as the documentation seems to suggest that (for many settings) + // setting it on one side of the stream automatically sets it for the other side of the stream. + // + // Although there isn't anything in the documentation to suggest that the second attempt would fail. + // + // Furthermore, this only seems to affect streams that are negotiating a security upgrade. + // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure + // connection, and then a startTLS is issued. + // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS). + + if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug. + { + [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]]; + return; + } + + if (![self openStreams]) + { + [self closeWithError:[self otherError:@"Error in CFStreamOpen"]]; + return; + } + + LogVerbose(@"Waiting for SSL Handshake to complete..."); } #endif @@ -7610,427 +7813,471 @@ + (void)ignore:(id)_ + (void)startCFStreamThreadIfNeeded { - LogTrace(); + LogTrace(); - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ + static dispatch_once_t predicate; + dispatch_once(&predicate, + ^{ - cfstreamThreadRetainCount = 0; - cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL); - }); + cfstreamThreadRetainCount = 0; + cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", + DISPATCH_QUEUE_SERIAL); + }); - dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool { + dispatch_sync(cfstreamThreadSetupQueue, + ^{ @autoreleasepool { - if (++cfstreamThreadRetainCount == 1) - { - cfstreamThread = [[NSThread alloc] initWithTarget:self - selector:@selector(cfstreamThread:) - object:nil]; - [cfstreamThread start]; - } - }}); + if (++cfstreamThreadRetainCount == 1) + { + cfstreamThread = [[NSThread alloc] initWithTarget:self + selector:@selector(cfstreamThread:) + object:nil]; + [cfstreamThread start]; + } + }}); } + (void)stopCFStreamThreadIfNeeded { - LogTrace(); + LogTrace(); - // The creation of the cfstreamThread is relatively expensive. - // So we'd like to keep it available for recycling. - // However, there's a tradeoff here, because it shouldn't remain alive forever. - // So what we're going to do is use a little delay before taking it down. - // This way it can be reused properly in situations where multiple sockets are continually in flux. + // The creation of the cfstreamThread is relatively expensive. + // So we'd like to keep it available for recycling. + // However, there's a tradeoff here, because it shouldn't remain alive forever. + // So what we're going to do is use a little delay before taking it down. + // This way it can be reused properly in situations where multiple sockets are continually in flux. - int delayInSeconds = 30; - dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { - #pragma clang diagnostic push - #pragma clang diagnostic warning "-Wimplicit-retain-self" + int delayInSeconds = 30; + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wimplicit-retain-self" - if (cfstreamThreadRetainCount == 0) - { - LogWarn(@"Logic error concerning cfstreamThread start / stop"); - return_from_block; - } + if (cfstreamThreadRetainCount == 0) + { + LogWarn(@"Logic error concerning cfstreamThread start / stop"); + return_from_block; + } - if (--cfstreamThreadRetainCount == 0) - { - [cfstreamThread cancel]; // set isCancelled flag + if (--cfstreamThreadRetainCount == 0) + { + [cfstreamThread cancel]; // set isCancelled flag - // wake up the thread - [[self class] performSelector:@selector(ignore:) - onThread:cfstreamThread - withObject:[NSNull null] - waitUntilDone:NO]; + // wake up the thread + [[self class] performSelector:@selector(ignore:) + onThread:cfstreamThread + withObject:[NSNull null] + waitUntilDone:NO]; - cfstreamThread = nil; - } + cfstreamThread = nil; + } - #pragma clang diagnostic pop - }}); +#pragma clang diagnostic pop + }}); } + (void)cfstreamThread:(id)unused { @autoreleasepool -{ - [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; + { + [[NSThread currentThread] setName:GCDAsyncSocketThreadName]; - LogInfo(@"CFStreamThread: Started"); + LogInfo(@"CFStreamThread: Started"); - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for decades. + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(ignore:) + userInfo:nil + repeats:YES]; - NSThread *currentThread = [NSThread currentThread]; - NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; + NSThread *currentThread = [NSThread currentThread]; + NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; - BOOL isCancelled = [currentThread isCancelled]; + BOOL isCancelled = [currentThread isCancelled]; - while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) - { - isCancelled = [currentThread isCancelled]; - } + while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) + { + isCancelled = [currentThread isCancelled]; + } - LogInfo(@"CFStreamThread: Stopped"); -}} + LogInfo(@"CFStreamThread: Stopped"); + }} + (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket { - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); + LogTrace(); + NSAssert([NSThread currentThread] == cfstreamThread, + @"Invoked on wrong thread"); - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - if (asyncSocket->readStream) - CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); + if (asyncSocket->readStream) + CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, + runLoop, + kCFRunLoopDefaultMode); - if (asyncSocket->writeStream) - CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); + if (asyncSocket->writeStream) + CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, + runLoop, + kCFRunLoopDefaultMode); } + (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket { - LogTrace(); - NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); + LogTrace(); + NSAssert([NSThread currentThread] == cfstreamThread, + @"Invoked on wrong thread"); - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - if (asyncSocket->readStream) - CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); + if (asyncSocket->readStream) + CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, + runLoop, + kCFRunLoopDefaultMode); - if (asyncSocket->writeStream) - CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); + if (asyncSocket->writeStream) + CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, + runLoop, + kCFRunLoopDefaultMode); } -static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +static void CFReadStreamCallback (CFReadStreamRef stream, + CFStreamEventType type, + void *pInfo) { - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventHasBytesAvailable: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSecureSocketHasBytesAvailable; - [asyncSocket doReadData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - Other"); - - if (asyncSocket->readStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } + switch(type) + { + case kCFStreamEventHasBytesAvailable: + { + dispatch_async(asyncSocket->socketQueue, + ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); + + if (asyncSocket->readStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. + // (A callback related to the tcp stream, but not to the SSL layer). + + if (CFReadStreamHasBytesAvailable(asyncSocket->readStream)) + { + asyncSocket->flags |= kSecureSocketHasBytesAvailable; + [asyncSocket cf_finishSSLHandshake]; + } + } + else + { + asyncSocket->flags |= kSecureSocketHasBytesAvailable; + [asyncSocket doReadData]; + } + }}); + + break; + } + default: + { + NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); + + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncSocket connectionClosedError]; + } + + dispatch_async(asyncSocket->socketQueue, + ^{ @autoreleasepool { + + LogCVerbose(@"CFReadStreamCallback - Other"); + + if (asyncSocket->readStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + [asyncSocket cf_abortSSLHandshake:error]; + } + else + { + [asyncSocket closeWithError:error]; + } + }}); + + break; + } + } #pragma clang diagnostic pop } -static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) +static void CFWriteStreamCallback (CFWriteStreamRef stream, + CFStreamEventType type, + void *pInfo) { - GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; + GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventCanAcceptBytes: - { - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. - // (A callback related to the tcp stream, but not to the SSL layer). - - if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket cf_finishSSLHandshake]; - } - } - else - { - asyncSocket->flags |= kSocketCanAcceptBytes; - [asyncSocket doWriteData]; - } - }}); - - break; - } - default: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncSocket connectionClosedError]; - } - - dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - Other"); - - if (asyncSocket->writeStream != stream) - return_from_block; - - if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) - { - [asyncSocket cf_abortSSLHandshake:error]; - } - else - { - [asyncSocket closeWithError:error]; - } - }}); - - break; - } - } + switch(type) + { + case kCFStreamEventCanAcceptBytes: + { + dispatch_async(asyncSocket->socketQueue, + ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); + + if (asyncSocket->writeStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie. + // (A callback related to the tcp stream, but not to the SSL layer). + + if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream)) + { + asyncSocket->flags |= kSocketCanAcceptBytes; + [asyncSocket cf_finishSSLHandshake]; + } + } + else + { + asyncSocket->flags |= kSocketCanAcceptBytes; + [asyncSocket doWriteData]; + } + }}); + + break; + } + default: + { + NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); + + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncSocket connectionClosedError]; + } + + dispatch_async(asyncSocket->socketQueue, + ^{ @autoreleasepool { + + LogCVerbose(@"CFWriteStreamCallback - Other"); + + if (asyncSocket->writeStream != stream) + return_from_block; + + if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS)) + { + [asyncSocket cf_abortSSLHandshake:error]; + } + else + { + [asyncSocket closeWithError:error]; + } + }}); + + break; + } + } #pragma clang diagnostic pop } - (BOOL)createReadAndWriteStream { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (readStream || writeStream) - { - // Streams already created - return YES; - } + if (readStream || writeStream) + { + // Streams already created + return YES; + } - int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; + int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN; - if (socketFD == SOCKET_NULL) - { - // Cannot create streams without a file descriptor - return NO; - } + if (socketFD == SOCKET_NULL) + { + // Cannot create streams without a file descriptor + return NO; + } - if (![self isConnected]) - { - // Cannot create streams until file descriptor is connected - return NO; - } + if (![self isConnected]) + { + // Cannot create streams until file descriptor is connected + return NO; + } - LogVerbose(@"Creating read and write stream..."); + LogVerbose(@"Creating read and write stream..."); - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream); + CFStreamCreatePairWithSocket(NULL, + (CFSocketNativeHandle)socketFD, + &readStream, + &writeStream); - // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). - // But let's not take any chances. + // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case). + // But let's not take any chances. - if (readStream) - CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - if (writeStream) - CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + if (readStream) + CFReadStreamSetProperty(readStream, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); + if (writeStream) + CFWriteStreamSetProperty(writeStream, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); - if ((readStream == NULL) || (writeStream == NULL)) - { - LogWarn(@"Unable to create read and write stream..."); + if ((readStream == NULL) || (writeStream == NULL)) + { + LogWarn(@"Unable to create read and write stream..."); - if (readStream) - { - CFReadStreamClose(readStream); - CFRelease(readStream); - readStream = NULL; - } - if (writeStream) - { - CFWriteStreamClose(writeStream); - CFRelease(writeStream); - writeStream = NULL; - } + if (readStream) + { + CFReadStreamClose(readStream); + CFRelease(readStream); + readStream = NULL; + } + if (writeStream) + { + CFWriteStreamClose(writeStream); + CFRelease(writeStream); + writeStream = NULL; + } - return NO; - } + return NO; + } - return YES; + return YES; } - (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite { - LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); - - streamContext.version = 0; - streamContext.info = (__bridge void *)(self); - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - readStreamEvents |= kCFStreamEventHasBytesAvailable; - - if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)) - { - return NO; - } + LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO")); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), + @"Read/Write stream is null"); + + streamContext.version = 0; + streamContext.info = (__bridge void *)(self); + streamContext.retain = nil; + streamContext.release = nil; + streamContext.copyDescription = nil; + + CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + if (includeReadWrite) + readStreamEvents |= kCFStreamEventHasBytesAvailable; + + if (!CFReadStreamSetClient(readStream, + readStreamEvents, + &CFReadStreamCallback, + &streamContext)) + { + return NO; + } - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - if (includeReadWrite) - writeStreamEvents |= kCFStreamEventCanAcceptBytes; + CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + if (includeReadWrite) + writeStreamEvents |= kCFStreamEventCanAcceptBytes; - if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)) - { - return NO; - } + if (!CFWriteStreamSetClient(writeStream, + writeStreamEvents, + &CFWriteStreamCallback, + &streamContext)) + { + return NO; + } - return YES; + return YES; } - (BOOL)addStreamsToRunLoop { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), + @"Read/Write stream is null"); - if (!(flags & kAddedStreamsToRunLoop)) - { - LogVerbose(@"Adding streams to runloop..."); + if (!(flags & kAddedStreamsToRunLoop)) + { + LogVerbose(@"Adding streams to runloop..."); - [[self class] startCFStreamThreadIfNeeded]; - dispatch_sync(cfstreamThreadSetupQueue, ^{ - [[self class] performSelector:@selector(scheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - }); - flags |= kAddedStreamsToRunLoop; - } + [[self class] startCFStreamThreadIfNeeded]; + dispatch_sync(cfstreamThreadSetupQueue, ^{ + [[self class] performSelector:@selector(scheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + }); + flags |= kAddedStreamsToRunLoop; + } - return YES; + return YES; } - (void)removeStreamsFromRunLoop { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), + @"Read/Write stream is null"); - if (flags & kAddedStreamsToRunLoop) - { - LogVerbose(@"Removing streams from runloop..."); + if (flags & kAddedStreamsToRunLoop) + { + LogVerbose(@"Removing streams from runloop..."); - dispatch_sync(cfstreamThreadSetupQueue, ^{ - [[self class] performSelector:@selector(unscheduleCFStreams:) - onThread:cfstreamThread - withObject:self - waitUntilDone:YES]; - }); - [[self class] stopCFStreamThreadIfNeeded]; + dispatch_sync(cfstreamThreadSetupQueue, ^{ + [[self class] performSelector:@selector(unscheduleCFStreams:) + onThread:cfstreamThread + withObject:self + waitUntilDone:YES]; + }); + [[self class] stopCFStreamThreadIfNeeded]; - flags &= ~kAddedStreamsToRunLoop; - } + flags &= ~kAddedStreamsToRunLoop; + } } - (BOOL)openStreams { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert((readStream != NULL && writeStream != NULL), + @"Read/Write stream is null"); - CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); - CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); + CFStreamStatus readStatus = CFReadStreamGetStatus(readStream); + CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream); - if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) - { - LogVerbose(@"Opening read and write stream..."); + if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) + { + LogVerbose(@"Opening read and write stream..."); - BOOL r1 = CFReadStreamOpen(readStream); - BOOL r2 = CFWriteStreamOpen(writeStream); + BOOL r1 = CFReadStreamOpen(readStream); + BOOL r2 = CFWriteStreamOpen(writeStream); - if (!r1 || !r2) - { - LogError(@"Error in CFStreamOpen"); - return NO; - } - } + if (!r1 || !r2) + { + LogError(@"Error in CFStreamOpen"); + return NO; + } + } - return YES; + return YES; } #endif @@ -8041,236 +8288,254 @@ - (BOOL)openStreams /** * See header file for big discussion of this method. -**/ + **/ - (BOOL)autoDisconnectOnClosedReadStream { - // Note: YES means kAllowHalfDuplexConnection is OFF + // Note: YES means kAllowHalfDuplexConnection is OFF - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return ((config & kAllowHalfDuplexConnection) == 0); - } - else - { - __block BOOL result; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return ((config & kAllowHalfDuplexConnection) == 0); + } + else + { + __block BOOL result; - dispatch_sync(socketQueue, ^{ - result = ((self->config & kAllowHalfDuplexConnection) == 0); - }); + dispatch_sync(socketQueue, ^{ + result = ((self->config & kAllowHalfDuplexConnection) == 0); + }); - return result; - } + return result; + } } /** * See header file for big discussion of this method. -**/ + **/ - (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag { - // Note: YES means kAllowHalfDuplexConnection is OFF + // Note: YES means kAllowHalfDuplexConnection is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (flag) - self->config &= ~kAllowHalfDuplexConnection; - else - self->config |= kAllowHalfDuplexConnection; - }; + if (flag) + self->config &= ~kAllowHalfDuplexConnection; + else + self->config |= kAllowHalfDuplexConnection; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } /** * See header file for big discussion of this method. -**/ + **/ - (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue { - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketNewTargetQueue, + IsOnSocketQueueOrTargetQueueKey, + nonNullUnusedPointer, + NULL); } /** * See header file for big discussion of this method. -**/ + **/ - (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue { - dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); + dispatch_queue_set_specific(socketOldTargetQueue, + IsOnSocketQueueOrTargetQueueKey, + NULL, + NULL); } /** * See header file for big discussion of this method. -**/ + **/ - (void)performBlock:(dispatch_block_t)block { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); } /** * Questions? Have you read the header file? -**/ + **/ - (int)socketFD { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return SOCKET_NULL; + } - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; + if (socket4FD != SOCKET_NULL) + return socket4FD; + else + return socket6FD; } /** * Questions? Have you read the header file? -**/ + **/ - (int)socket4FD { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return SOCKET_NULL; + } - return socket4FD; + return socket4FD; } /** * Questions? Have you read the header file? -**/ + **/ - (int)socket6FD { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return SOCKET_NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return SOCKET_NULL; + } - return socket6FD; + return socket6FD; } #if TARGET_OS_IPHONE /** * Questions? Have you read the header file? -**/ + **/ - (CFReadStreamRef)readStream { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return NULL; + } - if (readStream == NULL) - [self createReadAndWriteStream]; + if (readStream == NULL) + [self createReadAndWriteStream]; - return readStream; + return readStream; } /** * Questions? Have you read the header file? -**/ + **/ - (CFWriteStreamRef)writeStream { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return NULL; + } - if (writeStream == NULL) - [self createReadAndWriteStream]; + if (writeStream == NULL) + [self createReadAndWriteStream]; - return writeStream; + return writeStream; } - (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat { - if (![self createReadAndWriteStream]) - { - // Error occurred creating streams (perhaps socket isn't open) - return NO; - } + if (![self createReadAndWriteStream]) + { + // Error occurred creating streams (perhaps socket isn't open) + return NO; + } - BOOL r1, r2; + BOOL r1, r2; - LogVerbose(@"Enabling backgrouding on socket"); + LogVerbose(@"Enabling backgrouding on socket"); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); - r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + r1 = CFReadStreamSetProperty(readStream, + kCFStreamNetworkServiceType, + kCFStreamNetworkServiceTypeVoIP); + r2 = CFWriteStreamSetProperty(writeStream, + kCFStreamNetworkServiceType, + kCFStreamNetworkServiceTypeVoIP); #pragma clang diagnostic pop - if (!r1 || !r2) - { - return NO; - } + if (!r1 || !r2) + { + return NO; + } - if (!caveat) - { - if (![self openStreams]) - { - return NO; - } - } + if (!caveat) + { + if (![self openStreams]) + { + return NO; + } + } - return YES; + return YES; } /** * Questions? Have you read the header file? -**/ + **/ - (BOOL)enableBackgroundingOnSocket { - LogTrace(); + LogTrace(); - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return NO; + } - return [self enableBackgroundingOnSocketWithCaveat:NO]; + return [self enableBackgroundingOnSocketWithCaveat:NO]; } - (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.??? { - // This method was created as a workaround for a bug in iOS. - // Apple has since fixed this bug. - // I'm not entirely sure which version of iOS they fixed it in... + // This method was created as a workaround for a bug in iOS. + // Apple has since fixed this bug. + // I'm not entirely sure which version of iOS they fixed it in... - LogTrace(); + LogTrace(); - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NO; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return NO; + } - return [self enableBackgroundingOnSocketWithCaveat:YES]; + return [self enableBackgroundingOnSocketWithCaveat:YES]; } #endif - (SSLContextRef)sslContext { - if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD); - return NULL; - } + if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", + THIS_METHOD); + return NULL; + } - return sslContext; + return sslContext; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -8279,258 +8544,267 @@ - (SSLContextRef)sslContext + (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr { - LogTrace(); - - NSMutableArray *addresses = nil; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in nativeAddr4; - nativeAddr4.sin_len = sizeof(struct sockaddr_in); - nativeAddr4.sin_family = AF_INET; - nativeAddr4.sin_port = htons(port); - nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); - - struct sockaddr_in6 nativeAddr6; - nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); - nativeAddr6.sin6_family = AF_INET6; - nativeAddr6.sin6_port = htons(port); - nativeAddr6.sin6_flowinfo = 0; - nativeAddr6.sin6_addr = in6addr_loopback; - nativeAddr6.sin6_scope_id = 0; - - // Wrap the native address structures - - NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - - addresses = [NSMutableArray arrayWithCapacity:2]; - [addresses addObject:address4]; - [addresses addObject:address6]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - struct addrinfo hints, *res, *res0; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - NSUInteger capacity = 0; - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { - capacity++; - } - } - - addresses = [NSMutableArray arrayWithCapacity:capacity]; - - for (res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address. - // Wrap the native address structure, and add to results. - - NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address4]; - } - else if (res->ai_family == AF_INET6) - { - // Fixes connection issues with IPv6 - // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 - - // Found IPv6 address. - // Wrap the native address structure, and add to results. - - struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; - in_port_t *portPtr = &sockaddr->sin6_port; - if ((portPtr != NULL) && (*portPtr == 0)) { - *portPtr = htons(port); - } - - NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - [addresses addObject:address6]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } - - if (errPtr) *errPtr = error; - return addresses; + LogTrace(); + + NSMutableArray *addresses = nil; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in nativeAddr4; + nativeAddr4.sin_len = sizeof(struct sockaddr_in); + nativeAddr4.sin_family = AF_INET; + nativeAddr4.sin_port = htons(port); + nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); + + struct sockaddr_in6 nativeAddr6; + nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); + nativeAddr6.sin6_family = AF_INET6; + nativeAddr6.sin6_port = htons(port); + nativeAddr6.sin6_flowinfo = 0; + nativeAddr6.sin6_addr = in6addr_loopback; + nativeAddr6.sin6_scope_id = 0; + + // Wrap the native address structures + + NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + + addresses = [NSMutableArray arrayWithCapacity:2]; + [addresses addObject:address4]; + [addresses addObject:address6]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + struct addrinfo hints, *res, *res0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + int gai_error = getaddrinfo([host UTF8String], + [portStr UTF8String], + &hints, + &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + NSUInteger capacity = 0; + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { + capacity++; + } + } + + addresses = [NSMutableArray arrayWithCapacity:capacity]; + + for (res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address. + // Wrap the native address structure, and add to results. + + NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address4]; + } + else if (res->ai_family == AF_INET6) + { + // Fixes connection issues with IPv6 + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + + // Found IPv6 address. + // Wrap the native address structure, and add to results. + + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + [addresses addObject:address6]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + if (errPtr) *errPtr = error; + return addresses; } + (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 { - char addrBuf[INET_ADDRSTRLEN]; + char addrBuf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } + if (inet_ntop(AF_INET, + &pSockaddr4->sin_addr, + addrBuf, + (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; } + (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 { - char addrBuf[INET6_ADDRSTRLEN]; + char addrBuf[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } + if (inet_ntop(AF_INET6, + &pSockaddr6->sin6_addr, + addrBuf, + (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; } + (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 { - return ntohs(pSockaddr4->sin_port); + return ntohs(pSockaddr4->sin_port); } + (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 { - return ntohs(pSockaddr6->sin6_port); + return ntohs(pSockaddr6->sin6_port); } + (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr { - NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; - return [NSURL fileURLWithPath:path]; + NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path]; + return [NSURL fileURLWithPath:path]; } + (NSString *)hostFromAddress:(NSData *)address { - NSString *host; + NSString *host; - if ([self getHost:&host port:NULL fromAddress:address]) - return host; - else - return nil; + if ([self getHost:&host port:NULL fromAddress:address]) + return host; + else + return nil; } + (uint16_t)portFromAddress:(NSData *)address { - uint16_t port; + uint16_t port; - if ([self getHost:NULL port:&port fromAddress:address]) - return port; - else - return 0; + if ([self getHost:NULL port:&port fromAddress:address]) + return port; + else + return 0; } + (BOOL)isIPv4Address:(NSData *)address { - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; - if (sockaddrX->sa_family == AF_INET) { - return YES; - } - } + if (sockaddrX->sa_family == AF_INET) { + return YES; + } + } - return NO; + return NO; } + (BOOL)isIPv6Address:(NSData *)address { - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; - if (sockaddrX->sa_family == AF_INET6) { - return YES; - } - } + if (sockaddrX->sa_family == AF_INET6) { + return YES; + } + } - return NO; + return NO; } + (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address { - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; } + (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address { - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *sockaddrX = [address bytes]; + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *sockaddrX = [address bytes]; - if (sockaddrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - struct sockaddr_in sockaddr4; - memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); + if (sockaddrX->sa_family == AF_INET) + { + if ([address length] >= sizeof(struct sockaddr_in)) + { + struct sockaddr_in sockaddr4; + memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4)); - if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; - if (afPtr) *afPtr = AF_INET; + if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4]; + if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4]; + if (afPtr) *afPtr = AF_INET; - return YES; - } - } - else if (sockaddrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - struct sockaddr_in6 sockaddr6; - memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); + return YES; + } + } + else if (sockaddrX->sa_family == AF_INET6) + { + if ([address length] >= sizeof(struct sockaddr_in6)) + { + struct sockaddr_in6 sockaddr6; + memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6)); - if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; - if (afPtr) *afPtr = AF_INET6; + if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6]; + if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6]; + if (afPtr) *afPtr = AF_INET6; - return YES; - } - } - } + return YES; + } + } + } - return NO; + return NO; } + (NSData *)CRLFData { - return [NSData dataWithBytes:"\x0D\x0A" length:2]; + return [NSData dataWithBytes:"\x0D\x0A" length:2]; } + (NSData *)CRData { - return [NSData dataWithBytes:"\x0D" length:1]; + return [NSData dataWithBytes:"\x0D" length:1]; } + (NSData *)LFData { - return [NSData dataWithBytes:"\x0A" length:1]; + return [NSData dataWithBytes:"\x0A" length:1]; } + (NSData *)ZeroData { - return [NSData dataWithBytes:"" length:1]; + return [NSData dataWithBytes:"" length:1]; } @end diff --git a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m index bc31e6415..529aa13ff 100755 --- a/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +++ b/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m @@ -16,11 +16,11 @@ #endif #if TARGET_OS_IPHONE - #import - #import - #import - // Note: CFStream APIs are still used for backgrounding support and are part of CoreFoundation - // kCFStreamPropertyShouldCloseNativeSocket is available from CoreFoundation/CFStream.h +#import +#import +#import +// Note: CFStream APIs are still used for backgrounding support and are part of CoreFoundation +// kCFStreamPropertyShouldCloseNativeSocket is available from CoreFoundation/CFStream.h #endif #import @@ -89,19 +89,19 @@ * Seeing a return statements within an inner block * can sometimes be mistaken for a return point of the enclosing method. * This makes inline blocks a bit easier to read. -**/ + **/ #define return_from_block return /** * A socket file descriptor is really just an integer. * It represents the index of the socket within the kernel. * This makes invalid file descriptor comparisons easier to read. -**/ + **/ #define SOCKET_NULL -1 /** * Just to type less code. -**/ + **/ #define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} @@ -115,34 +115,34 @@ enum GCDAsyncUdpSocketFlags { - kDidCreateSockets = 1 << 0, // If set, the sockets have been created. - kDidBind = 1 << 1, // If set, bind has been called. - kConnecting = 1 << 2, // If set, a connection attempt is in progress. - kDidConnect = 1 << 3, // If set, socket is connected. - kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled - kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled - kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. - kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. - kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. - kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. - kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. - kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. - kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. - kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. - kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. - kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. - kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. + kDidCreateSockets = 1 << 0, // If set, the sockets have been created. + kDidBind = 1 << 1, // If set, bind has been called. + kConnecting = 1 << 2, // If set, a connection attempt is in progress. + kDidConnect = 1 << 3, // If set, socket is connected. + kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled + kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled + kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. + kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. + kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. + kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. + kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. + kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. + kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. + kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. + kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. + kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. + kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. #if TARGET_OS_IPHONE - kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread + kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread #endif }; enum GCDAsyncUdpSocketConfig { - kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled - kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled - kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 - kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 + kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled + kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled + kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 + kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -152,71 +152,71 @@ @interface GCDAsyncUdpSocket () { #if __has_feature(objc_arc_weak) - __weak id delegate; + __weak id delegate; #else - __unsafe_unretained id delegate; + __unsafe_unretained id delegate; #endif - dispatch_queue_t delegateQueue; + dispatch_queue_t delegateQueue; - GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; - dispatch_queue_t receiveFilterQueue; - BOOL receiveFilterAsync; + GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; + dispatch_queue_t receiveFilterQueue; + BOOL receiveFilterAsync; - GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; - dispatch_queue_t sendFilterQueue; - BOOL sendFilterAsync; + GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; + dispatch_queue_t sendFilterQueue; + BOOL sendFilterAsync; - uint32_t flags; - uint16_t config; + uint32_t flags; + uint16_t config; - uint16_t max4ReceiveSize; - uint32_t max6ReceiveSize; + uint16_t max4ReceiveSize; + uint32_t max6ReceiveSize; - uint16_t maxSendSize; + uint16_t maxSendSize; - int socket4FD; - int socket6FD; + int socket4FD; + int socket6FD; - dispatch_queue_t socketQueue; + dispatch_queue_t socketQueue; - dispatch_source_t send4Source; - dispatch_source_t send6Source; - dispatch_source_t receive4Source; - dispatch_source_t receive6Source; - dispatch_source_t sendTimer; + dispatch_source_t send4Source; + dispatch_source_t send6Source; + dispatch_source_t receive4Source; + dispatch_source_t receive6Source; + dispatch_source_t sendTimer; - GCDAsyncUdpSendPacket *currentSend; - NSMutableArray *sendQueue; + GCDAsyncUdpSendPacket *currentSend; + NSMutableArray *sendQueue; - unsigned long socket4FDBytesAvailable; - unsigned long socket6FDBytesAvailable; + unsigned long socket4FDBytesAvailable; + unsigned long socket6FDBytesAvailable; - uint32_t pendingFilterOperations; + uint32_t pendingFilterOperations; - NSData *cachedLocalAddress4; - NSString *cachedLocalHost4; - uint16_t cachedLocalPort4; + NSData *cachedLocalAddress4; + NSString *cachedLocalHost4; + uint16_t cachedLocalPort4; - NSData *cachedLocalAddress6; - NSString *cachedLocalHost6; - uint16_t cachedLocalPort6; + NSData *cachedLocalAddress6; + NSString *cachedLocalHost6; + uint16_t cachedLocalPort6; - NSData *cachedConnectedAddress; - NSString *cachedConnectedHost; - uint16_t cachedConnectedPort; - int cachedConnectedFamily; + NSData *cachedConnectedAddress; + NSString *cachedConnectedHost; + uint16_t cachedConnectedPort; + int cachedConnectedFamily; - void *IsOnSocketQueueOrTargetQueueKey; + void *IsOnSocketQueueOrTargetQueueKey; #if TARGET_OS_IPHONE - CFStreamClientContext streamContext; - CFReadStreamRef readStream4; - CFReadStreamRef readStream6; - CFWriteStreamRef writeStream4; - CFWriteStreamRef writeStream6; + CFStreamClientContext streamContext; + CFReadStreamRef readStream4; + CFReadStreamRef readStream6; + CFWriteStreamRef writeStream4; + CFWriteStreamRef writeStream6; #endif - id userData; + id userData; } - (void)resumeSend4Source; @@ -269,21 +269,21 @@ + (void)listenerThread:(id)unused; /** * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. -**/ + **/ @interface GCDAsyncUdpSendPacket : NSObject { @public - NSData *buffer; - NSTimeInterval timeout; - long tag; + NSData *buffer; + NSTimeInterval timeout; + long tag; - BOOL resolveInProgress; - BOOL filterInProgress; + BOOL resolveInProgress; + BOOL filterInProgress; - NSArray *resolvedAddresses; - NSError *resolveError; + NSArray *resolvedAddresses; + NSError *resolveError; - NSData *address; - int addressFamily; + NSData *address; + int addressFamily; } - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER; @@ -295,21 +295,21 @@ @implementation GCDAsyncUdpSendPacket // Cover the superclass' designated initializer - (instancetype)init NS_UNAVAILABLE { - NSAssert(0, @"Use the designated initializer"); - return nil; + NSAssert(0, @"Use the designated initializer"); + return nil; } - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i { - if ((self = [super init])) - { - buffer = d; - timeout = t; - tag = i; + if ((self = [super init])) + { + buffer = d; + timeout = t; + tag = i; - resolveInProgress = NO; - } - return self; + resolveInProgress = NO; + } + return self; } @@ -321,12 +321,12 @@ - (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i @interface GCDAsyncUdpSpecialPacket : NSObject { @public -// uint8_t type; + // uint8_t type; - BOOL resolveInProgress; + BOOL resolveInProgress; - NSArray *addresses; - NSError *error; + NSArray *addresses; + NSError *error; } - (instancetype)init NS_DESIGNATED_INITIALIZER; @@ -337,8 +337,8 @@ @implementation GCDAsyncUdpSpecialPacket - (instancetype)init { - self = [super init]; - return self; + self = [super init]; + return self; } @@ -352,134 +352,139 @@ @implementation GCDAsyncUdpSocket - (instancetype)init { - LogTrace(); + LogTrace(); - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; } - (instancetype)initWithSocketQueue:(dispatch_queue_t)sq { - LogTrace(); + LogTrace(); - return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; + return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; } - (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq { - LogTrace(); + LogTrace(); - return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; + return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; } - (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq { - LogTrace(); - - if ((self = [super init])) - { - delegate = aDelegate; - - if (dq) - { - delegateQueue = dq; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(delegateQueue); - #endif - } - - max4ReceiveSize = 65535; - max6ReceiveSize = 65535; - - maxSendSize = 65535; - - socket4FD = SOCKET_NULL; - socket6FD = SOCKET_NULL; - - if (sq) - { - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - @"The given socketQueue parameter must not be a concurrent queue."); - - socketQueue = sq; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(socketQueue); - #endif - } - else - { - socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL); - } - - // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. - // From the documentation: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // - // We're just going to use the memory address of an ivar. - // Specifically an ivar that is explicitly named for our purpose to make the code more readable. - // - // However, it feels tedious (and less readable) to include the "&" all the time: - // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) - // - // So we're going to make it so it doesn't matter if we use the '&' or not, - // by assigning the value of the ivar to the address of the ivar. - // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; - - IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; - - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); - - currentSend = nil; - sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; - - #if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - #endif - } - return self; + LogTrace(); + + if ((self = [super init])) + { + delegate = aDelegate; + + if (dq) + { + delegateQueue = dq; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(delegateQueue); +#endif + } + + max4ReceiveSize = 65535; + max6ReceiveSize = 65535; + + maxSendSize = 65535; + + socket4FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; + + if (sq) + { + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), + @"The given socketQueue parameter must not be a concurrent queue."); + NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0), + @"The given socketQueue parameter must not be a concurrent queue."); + + socketQueue = sq; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(socketQueue); +#endif + } + else + { + socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], + NULL); + } + + // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. + // From the documentation: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // + // We're just going to use the memory address of an ivar. + // Specifically an ivar that is explicitly named for our purpose to make the code more readable. + // + // However, it feels tedious (and less readable) to include the "&" all the time: + // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) + // + // So we're going to make it so it doesn't matter if we use the '&' or not, + // by assigning the value of the ivar to the address of the ivar. + // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; + + IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; + + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketQueue, + IsOnSocketQueueOrTargetQueueKey, + nonNullUnusedPointer, + NULL); + + currentSend = nil; + sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; + +#if TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; +#endif + } + return self; } - (void)dealloc { - LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); + LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); #if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; #endif - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - [self closeWithError:nil]; - } - else - { - dispatch_sync(socketQueue, ^{ - [self closeWithError:nil]; - }); - } - - delegate = nil; - #if !OS_OBJECT_USE_OBJC - if (delegateQueue) dispatch_release(delegateQueue); - #endif - delegateQueue = NULL; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + [self closeWithError:nil]; + } + else + { + dispatch_sync(socketQueue, ^{ + [self closeWithError:nil]; + }); + } + + delegate = nil; +#if !OS_OBJECT_USE_OBJC + if (delegateQueue) dispatch_release(delegateQueue); +#endif + delegateQueue = NULL; - #if !OS_OBJECT_USE_OBJC - if (socketQueue) dispatch_release(socketQueue); - #endif - socketQueue = NULL; +#if !OS_OBJECT_USE_OBJC + if (socketQueue) dispatch_release(socketQueue); +#endif + socketQueue = NULL; - LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); + LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -488,461 +493,461 @@ - (void)dealloc - (id)delegate { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegate; - } - else - { - __block id result = nil; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegate; + } + else + { + __block id result = nil; - dispatch_sync(socketQueue, ^{ - result = self->delegate; - }); + dispatch_sync(socketQueue, ^{ + result = self->delegate; + }); - return result; - } + return result; + } } - (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ - self->delegate = newDelegate; - }; + dispatch_block_t block = ^{ + self->delegate = newDelegate; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegate:(id)newDelegate { - [self setDelegate:newDelegate synchronously:NO]; + [self setDelegate:newDelegate synchronously:NO]; } - (void)synchronouslySetDelegate:(id)newDelegate { - [self setDelegate:newDelegate synchronously:YES]; + [self setDelegate:newDelegate synchronously:YES]; } - (dispatch_queue_t)delegateQueue { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - return delegateQueue; - } - else - { - __block dispatch_queue_t result = NULL; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + return delegateQueue; + } + else + { + __block dispatch_queue_t result = NULL; - dispatch_sync(socketQueue, ^{ - result = self->delegateQueue; - }); + dispatch_sync(socketQueue, ^{ + result = self->delegateQueue; + }); - return result; - } + return result; + } } - (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - #if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); +#endif - self->delegateQueue = newDelegateQueue; - }; + self->delegateQueue = newDelegateQueue; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegateQueue:newDelegateQueue synchronously:NO]; + [self setDelegateQueue:newDelegateQueue synchronously:NO]; } - (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegateQueue:newDelegateQueue synchronously:YES]; + [self setDelegateQueue:newDelegateQueue synchronously:YES]; } - (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - if (delegatePtr) *delegatePtr = delegate; - if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; - } - else - { - __block id dPtr = NULL; - __block dispatch_queue_t dqPtr = NULL; + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + if (delegatePtr) *delegatePtr = delegate; + if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; + } + else + { + __block id dPtr = NULL; + __block dispatch_queue_t dqPtr = NULL; - dispatch_sync(socketQueue, ^{ - dPtr = self->delegate; - dqPtr = self->delegateQueue; - }); + dispatch_sync(socketQueue, ^{ + dPtr = self->delegate; + dqPtr = self->delegateQueue; + }); - if (delegatePtr) *delegatePtr = dPtr; - if (delegateQueuePtr) *delegateQueuePtr = dqPtr; - } + if (delegatePtr) *delegatePtr = dPtr; + if (delegateQueuePtr) *delegateQueuePtr = dqPtr; + } } - (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - self->delegate = newDelegate; + self->delegate = newDelegate; - #if !OS_OBJECT_USE_OBJC - if (self->delegateQueue) dispatch_release(self->delegateQueue); - if (newDelegateQueue) dispatch_retain(newDelegateQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->delegateQueue) dispatch_release(self->delegateQueue); + if (newDelegateQueue) dispatch_retain(newDelegateQueue); +#endif - self->delegateQueue = newDelegateQueue; - }; + self->delegateQueue = newDelegateQueue; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { - block(); - } - else { - if (synchronously) - dispatch_sync(socketQueue, block); - else - dispatch_async(socketQueue, block); - } + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { + block(); + } + else { + if (synchronously) + dispatch_sync(socketQueue, block); + else + dispatch_async(socketQueue, block); + } } - (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; } - (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue { - [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; + [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; } - (BOOL)isIPv4Enabled { - // Note: YES means kIPv4Disabled is OFF + // Note: YES means kIPv4Disabled is OFF - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = ((self->config & kIPv4Disabled) == 0); - }; + result = ((self->config & kIPv4Disabled) == 0); + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setIPv4Enabled:(BOOL)flag { - // Note: YES means kIPv4Disabled is OFF + // Note: YES means kIPv4Disabled is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - if (flag) - self->config &= ~kIPv4Disabled; - else - self->config |= kIPv4Disabled; - }; + if (flag) + self->config &= ~kIPv4Disabled; + else + self->config |= kIPv4Disabled; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (BOOL)isIPv6Enabled { - // Note: YES means kIPv6Disabled is OFF + // Note: YES means kIPv6Disabled is OFF - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = ((self->config & kIPv6Disabled) == 0); - }; + result = ((self->config & kIPv6Disabled) == 0); + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setIPv6Enabled:(BOOL)flag { - // Note: YES means kIPv6Disabled is OFF + // Note: YES means kIPv6Disabled is OFF - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); + LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); - if (flag) - self->config &= ~kIPv6Disabled; - else - self->config |= kIPv6Disabled; - }; + if (flag) + self->config &= ~kIPv6Disabled; + else + self->config |= kIPv6Disabled; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (BOOL)isIPv4Preferred { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->config & kPreferIPv4) ? YES : NO; - }; + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv4) ? YES : NO; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isIPv6Preferred { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->config & kPreferIPv6) ? YES : NO; - }; + dispatch_block_t block = ^{ + result = (self->config & kPreferIPv6) ? YES : NO; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isIPVersionNeutral { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; - }; + dispatch_block_t block = ^{ + result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setPreferIPv4 { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogTrace(); + LogTrace(); - self->config |= kPreferIPv4; - self->config &= ~kPreferIPv6; + self->config |= kPreferIPv4; + self->config &= ~kPreferIPv6; - }; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)setPreferIPv6 { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogTrace(); + LogTrace(); - self->config &= ~kPreferIPv4; - self->config |= kPreferIPv6; + self->config &= ~kPreferIPv4; + self->config |= kPreferIPv6; - }; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)setIPVersionNeutral { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogTrace(); + LogTrace(); - self->config &= ~kPreferIPv4; - self->config &= ~kPreferIPv6; + self->config &= ~kPreferIPv4; + self->config &= ~kPreferIPv6; - }; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (uint16_t)maxReceiveIPv4BufferSize { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = self->max4ReceiveSize; - }; + result = self->max4ReceiveSize; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setMaxReceiveIPv4BufferSize:(uint16_t)max { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - self->max4ReceiveSize = max; - }; + self->max4ReceiveSize = max; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (uint32_t)maxReceiveIPv6BufferSize { - __block uint32_t result = 0; + __block uint32_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = self->max6ReceiveSize; - }; + result = self->max6ReceiveSize; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setMaxReceiveIPv6BufferSize:(uint32_t)max { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - self->max6ReceiveSize = max; - }; + self->max6ReceiveSize = max; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)setMaxSendBufferSize:(uint16_t)max { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); + LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); - self->maxSendSize = max; - }; + self->maxSendSize = max; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (uint16_t)maxSendBufferSize { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = self->maxSendSize; - }; + result = self->maxSendSize; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (id)userData { - __block id result = nil; + __block id result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = self->userData; - }; + result = self->userData; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (void)setUserData:(id)arbitraryUserData { - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->userData != arbitraryUserData) - { - self->userData = arbitraryUserData; - } - }; + if (self->userData != arbitraryUserData) + { + self->userData = arbitraryUserData; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -951,90 +956,91 @@ - (void)setUserData:(id)arbitraryUserData - (void)notifyDidConnectToAddress:(NSData *)anAddress { - LogTrace(); + LogTrace(); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) - { - NSData *address = [anAddress copy]; // In case param is NSMutableData + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) + { + NSData *address = [anAddress copy]; // In case param is NSMutableData - dispatch_async(delegateQueue, ^{ @autoreleasepool { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate udpSocket:self didConnectToAddress:address]; - }}); - } + [theDelegate udpSocket:self didConnectToAddress:address]; + }}); + } } - (void)notifyDidNotConnect:(NSError *)error { - LogTrace(); + LogTrace(); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate udpSocket:self didNotConnect:error]; - }}); - } + [theDelegate udpSocket:self didNotConnect:error]; + }}); + } } - (void)notifyDidSendDataWithTag:(long)tag { - LogTrace(); + LogTrace(); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate udpSocket:self didSendDataWithTag:tag]; - }}); - } + [theDelegate udpSocket:self didSendDataWithTag:tag]; + }}); + } } - (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error { - LogTrace(); + LogTrace(); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; - }}); - } + [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; + }}); + } } - (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context { - LogTrace(); + LogTrace(); - SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); + SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:selector]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:selector]) + { + dispatch_async(delegateQueue, + ^{ @autoreleasepool { - [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; - }}); - } + [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; + }}); + } } - (void)notifyDidCloseWithError:(NSError *)error { - LogTrace(); + LogTrace(); - __strong id theDelegate = delegate; - if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) - { - dispatch_async(delegateQueue, ^{ @autoreleasepool { + __strong id theDelegate = delegate; + if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) + { + dispatch_async(delegateQueue, ^{ @autoreleasepool { - [theDelegate udpSocketDidClose:self withError:error]; - }}); - } + [theDelegate udpSocketDidClose:self withError:error]; + }}); + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1043,83 +1049,87 @@ - (void)notifyDidCloseWithError:(NSError *)error - (NSError *)badConfigError:(NSString *)errMsg { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadConfigError - userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadConfigError + userInfo:userInfo]; } - (NSError *)badParamError:(NSString *)errMsg { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketBadParamError - userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketBadParamError + userInfo:userInfo]; } - (NSError *)gaiError:(int)gai_error { - NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; + return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; } - (NSError *)errnoErrorWithReason:(NSString *)reason { - NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; - NSDictionary *userInfo; + NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; + NSDictionary *userInfo; - if (reason) - userInfo = @{NSLocalizedDescriptionKey : errMsg, - NSLocalizedFailureReasonErrorKey : reason}; - else - userInfo = @{NSLocalizedDescriptionKey : errMsg}; + if (reason) + userInfo = @{NSLocalizedDescriptionKey : errMsg, + NSLocalizedFailureReasonErrorKey : reason}; + else + userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; } - (NSError *)errnoError { - return [self errnoErrorWithReason:nil]; + return [self errnoErrorWithReason:nil]; } /** * Returns a standard send timeout error. -**/ + **/ - (NSError *)sendTimeoutError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", - @"GCDAsyncUdpSocket", [NSBundle mainBundle], - @"Send operation timed out", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", + @"GCDAsyncUdpSocket", + [NSBundle mainBundle], + @"Send operation timed out", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketSendTimeoutError - userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketSendTimeoutError + userInfo:userInfo]; } - (NSError *)socketClosedError { - NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", - @"GCDAsyncUdpSocket", [NSBundle mainBundle], - @"Socket closed", nil); + NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", + @"GCDAsyncUdpSocket", + [NSBundle mainBundle], + @"Socket closed", + nil); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; } - (NSError *)otherError:(NSString *)errMsg { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg}; - return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain - code:GCDAsyncUdpSocketOtherError - userInfo:userInfo]; + return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain + code:GCDAsyncUdpSocketOtherError + userInfo:userInfo]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1128,153 +1138,162 @@ - (NSError *)otherError:(NSString *)errMsg - (BOOL)preOp:(NSError **)errPtr { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (delegate == nil) // Must have delegate set - { - if (errPtr) - { - NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if (delegateQueue == NULL) // Must have delegate queue set - { - if (errPtr) - { - NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } + if (delegate == nil) // Must have delegate set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if (delegateQueue == NULL) // Must have delegate queue set + { + if (errPtr) + { + NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } - return YES; + return YES; } /** * This method executes on a global concurrent queue. * When complete, it executes the given completion block on the socketQueue. -**/ + **/ - (void)asyncResolveHost:(NSString *)aHost port:(uint16_t)port - withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock -{ - LogTrace(); - - // Check parameter(s) - - if (aHost == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - NSError *error = [self badParamError:msg]; - - // We should still use dispatch_async since this method is expected to be asynchronous - - dispatch_async(socketQueue, ^{ @autoreleasepool { - - completionBlock(nil, error); - }}); - - return; - } - - // It's possible that the given aHost parameter is actually a NSMutableString. - // So we want to copy it now, within this block that will be executed synchronously. - // This way the asynchronous lookup block below doesn't have to worry about it changing. - - NSString *host = [aHost copy]; - - - dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { - - NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; - NSError *error = nil; - - if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) - { - // Use LOOPBACK address - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; - - // Wrap the native address structures and add to list - [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; - [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; - } - else - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - 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; - - int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); - - if (gai_error) - { - error = [self gaiError:gai_error]; - } - else - { - for(res = res0; res; res = res->ai_next) - { - if (res->ai_family == AF_INET) - { - // Found IPv4 address - // Wrap the native address structure and add to list - - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - else if (res->ai_family == AF_INET6) - { - - // Fixes connection issues with IPv6, it is the same solution for udp socket. - // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 - struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; - in_port_t *portPtr = &sockaddr->sin6_port; - if ((portPtr != NULL) && (*portPtr == 0)) { - *portPtr = htons(port); - } - - // Found IPv6 address - // Wrap the native address structure and add to list - [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; - } - } - freeaddrinfo(res0); - - if ([addresses count] == 0) - { - error = [self gaiError:EAI_FAIL]; - } - } - } + withCompletionBlock:(void (^)(NSArray *addresses, + NSError *error))completionBlock +{ + LogTrace(); - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + // Check parameter(s) + + if (aHost == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + NSError *error = [self badParamError:msg]; - completionBlock(addresses, error); - }}); + // We should still use dispatch_async since this method is expected to be asynchronous - }}); + dispatch_async(socketQueue, ^{ @autoreleasepool { + + completionBlock(nil, error); + }}); + + return; + } + + // It's possible that the given aHost parameter is actually a NSMutableString. + // So we want to copy it now, within this block that will be executed synchronously. + // This way the asynchronous lookup block below doesn't have to worry about it changing. + + NSString *host = [aHost copy]; + + + dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, + 0); + dispatch_async(globalConcurrentQueue, + ^{ @autoreleasepool { + + NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; + NSError *error = nil; + + if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) + { + // Use LOOPBACK address + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + // Wrap the native address structures and add to list + [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; + [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; + } + else + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + 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; + + int gai_error = getaddrinfo([host UTF8String], + [portStr UTF8String], + &hints, + &res0); + + if (gai_error) + { + error = [self gaiError:gai_error]; + } + else + { + for(res = res0; res; res = res->ai_next) + { + if (res->ai_family == AF_INET) + { + // Found IPv4 address + // Wrap the native address structure and add to list + + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + else if (res->ai_family == AF_INET6) + { + + // Fixes connection issues with IPv6, it is the same solution for udp socket. + // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 + struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr; + in_port_t *portPtr = &sockaddr->sin6_port; + if ((portPtr != NULL) && (*portPtr == 0)) { + *portPtr = htons(port); + } + + // Found IPv6 address + // Wrap the native address structure and add to list + [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; + } + } + freeaddrinfo(res0); + + if ([addresses count] == 0) + { + error = [self gaiError:EAI_FAIL]; + } + } + } + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + completionBlock(addresses, error); + }}); + + }}); } /** @@ -1283,1016 +1302,1072 @@ - (void)asyncResolveHost:(NSString *)aHost * * Returns the address family (AF_INET or AF_INET6) of the picked address, * or AF_UNSPEC and the corresponding error is there's a problem. -**/ + **/ - (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert([addresses count] > 0, @"Expected at least one address"); - - int resultAF = AF_UNSPEC; - NSData *resultAddress = nil; - NSError *resultError = nil; - - // Check for problems + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert([addresses count] > 0, @"Expected at least one address"); - BOOL resolvedIPv4Address = NO; - BOOL resolvedIPv6Address = NO; + int resultAF = AF_UNSPEC; + NSData *resultAddress = nil; + NSError *resultError = nil; + + // Check for problems - for (NSData *address in addresses) - { - switch ([[self class] familyFromAddress:address]) - { - case AF_INET : resolvedIPv4Address = YES; break; - case AF_INET6 : resolvedIPv6Address = YES; break; + BOOL resolvedIPv4Address = NO; + BOOL resolvedIPv6Address = NO; - default : NSAssert(NO, @"Addresses array contains invalid address"); - } - } + for (NSData *address in addresses) + { + switch ([[self class] familyFromAddress:address]) + { + case AF_INET : resolvedIPv4Address = YES; break; + case AF_INET6 : resolvedIPv6Address = YES; break; - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + default : NSAssert(NO, @"Addresses array contains invalid address"); + } + } - if (isIPv4Disabled && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; - - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; + + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; + + return resultAF; + } - return resultAF; - } + if (isIPv6Disabled && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; - if (isIPv6Disabled && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; + return resultAF; + } - return resultAF; - } + BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; + BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; - BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; - BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; + if (isIPv4Deactivated && !resolvedIPv6Address) + { + NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; + resultError = [self otherError:msg]; - if (isIPv4Deactivated && !resolvedIPv6Address) - { - NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; - resultError = [self otherError:msg]; + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; + return resultAF; + } - return resultAF; - } + if (isIPv6Deactivated && !resolvedIPv4Address) + { + NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; + resultError = [self otherError:msg]; - if (isIPv6Deactivated && !resolvedIPv4Address) - { - NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; - resultError = [self otherError:msg]; + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; + return resultAF; + } - return resultAF; - } + // Extract first IPv4 and IPv6 address in list - // Extract first IPv4 and IPv6 address in list + BOOL ipv4WasFirstInList = YES; + NSData *address4 = nil; + NSData *address6 = nil; - BOOL ipv4WasFirstInList = YES; - NSData *address4 = nil; - NSData *address6 = nil; + for (NSData *address in addresses) + { + int af = [[self class] familyFromAddress:address]; - for (NSData *address in addresses) - { - int af = [[self class] familyFromAddress:address]; + if (af == AF_INET) + { + if (address4 == nil) + { + address4 = address; - if (af == AF_INET) - { - if (address4 == nil) - { - address4 = address; + if (address6) + break; + else + ipv4WasFirstInList = YES; + } + } + else // af == AF_INET6 + { + if (address6 == nil) + { + address6 = address; - if (address6) - break; - else - ipv4WasFirstInList = YES; - } - } - else // af == AF_INET6 - { - if (address6 == nil) - { - address6 = address; + if (address4) + break; + else + ipv4WasFirstInList = NO; + } + } + } - if (address4) - break; - else - ipv4WasFirstInList = NO; - } - } - } + // Determine socket type - // Determine socket type + BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; + BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; + + BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); + BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); - BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; - BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; - - BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); - BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); + NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); + NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); - NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); - NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); + if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) + { + resultAF = AF_INET; + resultAddress = address4; + } + else + { + resultAF = AF_INET6; + resultAddress = address6; + } - if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) - { - resultAF = AF_INET; - resultAddress = address4; - } - else - { - resultAF = AF_INET6; - resultAddress = address6; - } + if (addressPtr) *addressPtr = resultAddress; + if (errorPtr) *errorPtr = resultError; - if (addressPtr) *addressPtr = resultAddress; - if (errorPtr) *errorPtr = resultError; - - return resultAF; + return resultAF; } /** * Finds the address(es) of an interface description. * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). -**/ + **/ - (void)convertIntefaceDescription:(NSString *)interfaceDescription port:(uint16_t)port intoAddress4:(NSData **)interfaceAddr4Ptr address6:(NSData **)interfaceAddr6Ptr { - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (interfaceDescription == nil) - { - // ANY address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(sockaddr4); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); - - sockaddr6.sin6_len = sizeof(sockaddr6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_any; - - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else if ([interfaceDescription isEqualToString:@"localhost"] || - [interfaceDescription isEqualToString:@"loopback"]) - { - // LOOPBACK address - - struct sockaddr_in sockaddr4; - memset(&sockaddr4, 0, sizeof(sockaddr4)); - - sockaddr4.sin_len = sizeof(struct sockaddr_in); - sockaddr4.sin_family = AF_INET; - sockaddr4.sin_port = htons(port); - sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - struct sockaddr_in6 sockaddr6; - memset(&sockaddr6, 0, sizeof(sockaddr6)); + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (interfaceDescription == nil) + { + // ANY address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(sockaddr4); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(sockaddr6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_any; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else if ([interfaceDescription isEqualToString:@"localhost"] || + [interfaceDescription isEqualToString:@"loopback"]) + { + // LOOPBACK address + + struct sockaddr_in sockaddr4; + memset(&sockaddr4, 0, sizeof(sockaddr4)); + + sockaddr4.sin_len = sizeof(struct sockaddr_in); + sockaddr4.sin_family = AF_INET; + sockaddr4.sin_port = htons(port); + sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct sockaddr_in6 sockaddr6; + memset(&sockaddr6, 0, sizeof(sockaddr6)); + + sockaddr6.sin6_len = sizeof(struct sockaddr_in6); + sockaddr6.sin6_family = AF_INET6; + sockaddr6.sin6_port = htons(port); + sockaddr6.sin6_addr = in6addr_loopback; + + addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; + } + else + { + const char *iface = [interfaceDescription UTF8String]; + + struct ifaddrs *addrs; + const struct ifaddrs *cursor; + + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) + { + // IPv4 - sockaddr6.sin6_len = sizeof(struct sockaddr_in6); - sockaddr6.sin6_family = AF_INET6; - sockaddr6.sin6_port = htons(port); - sockaddr6.sin6_addr = in6addr_loopback; + struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; - addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; - } - else - { - const char *iface = [interfaceDescription UTF8String]; + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match - struct ifaddrs *addrs; - const struct ifaddrs *cursor; + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) - { - // IPv4 + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + else + { + char ip[INET_ADDRSTRLEN]; - struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr; + const char *conversion; + conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); + struct sockaddr_in nativeAddr4 = *addr; + nativeAddr4.sin_port = htons(port); - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - else - { - char ip[INET_ADDRSTRLEN]; + addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; + } + } + } + else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) + { + // IPv6 - const char *conversion; - conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match + if (strcmp(cursor->ifa_name, iface) == 0) + { + // Name match - struct sockaddr_in nativeAddr4 = *addr; - nativeAddr4.sin_port = htons(port); + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); - addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; - } - } - } - else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) - { - // IPv6 + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + else + { + char ip[INET6_ADDRSTRLEN]; - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; - - if (strcmp(cursor->ifa_name, iface) == 0) - { - // Name match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - else - { - char ip[INET6_ADDRSTRLEN]; + const char *conversion; + conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); - const char *conversion; - conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); - - if ((conversion != NULL) && (strcmp(ip, iface) == 0)) - { - // IP match - - struct sockaddr_in6 nativeAddr6 = *addr; - nativeAddr6.sin6_port = htons(port); - - addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; - } - } - } + if ((conversion != NULL) && (strcmp(ip, iface) == 0)) + { + // IP match - cursor = cursor->ifa_next; - } + struct sockaddr_in6 nativeAddr6 = *addr; + nativeAddr6.sin6_port = htons(port); - freeifaddrs(addrs); - } - } + addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; + } + } + } + + cursor = cursor->ifa_next; + } - if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; - if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; + freeifaddrs(addrs); + } + } + + if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; + if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; } /** * Converts a numeric hostname into its corresponding address. * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) -**/ + **/ - (void)convertNumericHost:(NSString *)numericHost port:(uint16_t)port intoAddress4:(NSData **)addr4Ptr address6:(NSData **)addr6Ptr { - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (numericHost) - { - NSString *portStr = [NSString stringWithFormat:@"%hu", port]; - - 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_NUMERICHOST; // No name resolution should be attempted - - if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0) - { - for (res = res0; res; res = res->ai_next) - { - if ((addr4 == nil) && (res->ai_family == AF_INET)) - { - // Found IPv4 address - // Wrap the native address structure - addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - else if ((addr6 == nil) && (res->ai_family == AF_INET6)) - { - // Found IPv6 address - // Wrap the native address structure - addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; - } - } - freeaddrinfo(res0); - } - } - - if (addr4Ptr) *addr4Ptr = addr4; - if (addr6Ptr) *addr6Ptr = addr6; + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (numericHost) + { + NSString *portStr = [NSString stringWithFormat:@"%hu", port]; + + 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_NUMERICHOST; // No name resolution should be attempted + + if (getaddrinfo([numericHost UTF8String], + [portStr UTF8String], + &hints, + &res0) == 0) + { + for (res = res0; res; res = res->ai_next) + { + if ((addr4 == nil) && (res->ai_family == AF_INET)) + { + // Found IPv4 address + // Wrap the native address structure + addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + else if ((addr6 == nil) && (res->ai_family == AF_INET6)) + { + // Found IPv6 address + // Wrap the native address structure + addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; + } + } + freeaddrinfo(res0); + } + } + + if (addr4Ptr) *addr4Ptr = addr4; + if (addr6Ptr) *addr6Ptr = addr6; } - (BOOL)isConnectedToAddress4:(NSData *)someAddr4 { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - if (cachedConnectedFamily != AF_INET) - { - return NO; - } + if (cachedConnectedFamily != AF_INET) + { + return NO; + } - const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; - const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; + const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes]; + const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes]; - if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0) - { - return NO; - } + if (memcmp(&sSockaddr4->sin_addr, + &cSockaddr4->sin_addr, + sizeof(struct in_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr4->sin_port, + &cSockaddr4->sin_port, + sizeof(in_port_t)) != 0) + { + return NO; + } - return YES; + return YES; } - (BOOL)isConnectedToAddress6:(NSData *)someAddr6 { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(flags & kDidConnect, @"Not connected"); - NSAssert(cachedConnectedAddress, @"Expected cached connected address"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(flags & kDidConnect, @"Not connected"); + NSAssert(cachedConnectedAddress, @"Expected cached connected address"); - if (cachedConnectedFamily != AF_INET6) - { - return NO; - } + if (cachedConnectedFamily != AF_INET6) + { + return NO; + } - const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; - const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; + const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes]; + const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes]; - if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) - { - return NO; - } - if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0) - { - return NO; - } + if (memcmp(&sSockaddr6->sin6_addr, + &cSockaddr6->sin6_addr, + sizeof(struct in6_addr)) != 0) + { + return NO; + } + if (memcmp(&sSockaddr6->sin6_port, + &cSockaddr6->sin6_port, + sizeof(in_port_t)) != 0) + { + return NO; + } - return YES; + return YES; } - (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 { - if (interfaceAddr4 == nil) - return 0; - if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) - return 0; + if (interfaceAddr4 == nil) + return 0; + if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) + return 0; - int result = 0; - const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + int result = 0; + const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes]; - struct ifaddrs *addrs; - const struct ifaddrs *cursor; + struct ifaddrs *addrs; + const struct ifaddrs *cursor; - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET) - { - // IPv4 + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET) + { + // IPv4 - const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; + const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr; - if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } + if (memcmp(&addr->sin_addr, + &ifaceAddr->sin_addr, + sizeof(struct in_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } - cursor = cursor->ifa_next; - } + cursor = cursor->ifa_next; + } - freeifaddrs(addrs); - } + freeifaddrs(addrs); + } - return result; + return result; } - (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 { - if (interfaceAddr6 == nil) - return 0; - if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) - return 0; + if (interfaceAddr6 == nil) + return 0; + if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) + return 0; - int result = 0; - const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; + int result = 0; + const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes]; - struct ifaddrs *addrs; - const struct ifaddrs *cursor; + struct ifaddrs *addrs; + const struct ifaddrs *cursor; - if ((getifaddrs(&addrs) == 0)) - { - cursor = addrs; - while (cursor != NULL) - { - if (cursor->ifa_addr->sa_family == AF_INET6) - { - // IPv6 + if ((getifaddrs(&addrs) == 0)) + { + cursor = addrs; + while (cursor != NULL) + { + if (cursor->ifa_addr->sa_family == AF_INET6) + { + // IPv6 - const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; + const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr; - if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) - { - result = if_nametoindex(cursor->ifa_name); - break; - } - } + if (memcmp(&addr->sin6_addr, + &ifaceAddr->sin6_addr, + sizeof(struct in6_addr)) == 0) + { + result = if_nametoindex(cursor->ifa_name); + break; + } + } - cursor = cursor->ifa_next; - } + cursor = cursor->ifa_next; + } - freeifaddrs(addrs); - } + freeifaddrs(addrs); + } - return result; + return result; } - (void)setupSendAndReceiveSourcesForSocket4 { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue); - receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool { - - LogVerbose(@"send4EventBlock"); - LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); - - self->flags |= kSock4CanAcceptBytes; - - // If we're ready to send data, do so immediately. - // Otherwise pause the send source or it will continue to fire over and over again. - - if (self->currentSend == nil) - { - LogVerbose(@"Nothing to send"); - [self suspendSend4Source]; - } - else if (self->currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend4Source]; - } - else if (self->currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend4Source]; - } - else - { - [self doSend]; - } + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, + socket4FD, + 0, + socketQueue); + receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + socket4FD, + 0, + socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send4Source, + ^{ @autoreleasepool { + + LogVerbose(@"send4EventBlock"); + LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", + dispatch_source_get_data(send4Source)); + + self->flags |= kSock4CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend4Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend4Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend4Source]; + } + else + { + [self doSend]; + } - }}); + }}); - dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool { + dispatch_source_set_event_handler(receive4Source, + ^{ @autoreleasepool { - LogVerbose(@"receive4EventBlock"); + LogVerbose(@"receive4EventBlock"); - self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); - LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); + self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source); + LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); - if (self->socket4FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; + if (self->socket4FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; - }}); + }}); - // Setup cancel handlers + // Setup cancel handlers - __block int socketFDRefCount = 2; + __block int socketFDRefCount = 2; - int theSocketFD = socket4FD; + int theSocketFD = socket4FD; - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send4Source; - dispatch_source_t theReceiveSource = receive4Source; - #endif +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send4Source; + dispatch_source_t theReceiveSource = receive4Source; +#endif - dispatch_source_set_cancel_handler(send4Source, ^{ + dispatch_source_set_cancel_handler(send4Source, ^{ - LogVerbose(@"send4CancelBlock"); + LogVerbose(@"send4CancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(send4Source)"); - dispatch_release(theSendSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send4Source)"); + dispatch_release(theSendSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); - dispatch_source_set_cancel_handler(receive4Source, ^{ + dispatch_source_set_cancel_handler(receive4Source, ^{ - LogVerbose(@"receive4CancelBlock"); + LogVerbose(@"receive4CancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(receive4Source)"); - dispatch_release(theReceiveSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive4Source)"); + dispatch_release(theReceiveSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket4FD)"); - close(theSocketFD); - } - }); + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket4FD)"); + close(theSocketFD); + } + }); - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. - socket4FDBytesAvailable = 0; - flags |= kSock4CanAcceptBytes; + socket4FDBytesAvailable = 0; + flags |= kSock4CanAcceptBytes; - flags |= kSend4SourceSuspended; - flags |= kReceive4SourceSuspended; + flags |= kSend4SourceSuspended; + flags |= kReceive4SourceSuspended; } - (void)setupSendAndReceiveSourcesForSocket6 { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue); - receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); - - // Setup event handlers - - dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool { - - LogVerbose(@"send6EventBlock"); - LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); - - self->flags |= kSock6CanAcceptBytes; - - // If we're ready to send data, do so immediately. - // Otherwise pause the send source or it will continue to fire over and over again. - - if (self->currentSend == nil) - { - LogVerbose(@"Nothing to send"); - [self suspendSend6Source]; - } - else if (self->currentSend->resolveInProgress) - { - LogVerbose(@"currentSend - waiting for address resolve"); - [self suspendSend6Source]; - } - else if (self->currentSend->filterInProgress) - { - LogVerbose(@"currentSend - waiting on sendFilter"); - [self suspendSend6Source]; - } - else - { - [self doSend]; - } + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, + socket6FD, + 0, + socketQueue); + receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, + socket6FD, + 0, + socketQueue); + + // Setup event handlers + + dispatch_source_set_event_handler(send6Source, + ^{ @autoreleasepool { + + LogVerbose(@"send6EventBlock"); + LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", + dispatch_source_get_data(send6Source)); + + self->flags |= kSock6CanAcceptBytes; + + // If we're ready to send data, do so immediately. + // Otherwise pause the send source or it will continue to fire over and over again. + + if (self->currentSend == nil) + { + LogVerbose(@"Nothing to send"); + [self suspendSend6Source]; + } + else if (self->currentSend->resolveInProgress) + { + LogVerbose(@"currentSend - waiting for address resolve"); + [self suspendSend6Source]; + } + else if (self->currentSend->filterInProgress) + { + LogVerbose(@"currentSend - waiting on sendFilter"); + [self suspendSend6Source]; + } + else + { + [self doSend]; + } - }}); + }}); - dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool { + dispatch_source_set_event_handler(receive6Source, + ^{ @autoreleasepool { - LogVerbose(@"receive6EventBlock"); + LogVerbose(@"receive6EventBlock"); - self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); - LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); + self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source); + LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); - if (self->socket6FDBytesAvailable > 0) - [self doReceive]; - else - [self doReceiveEOF]; + if (self->socket6FDBytesAvailable > 0) + [self doReceive]; + else + [self doReceiveEOF]; - }}); + }}); - // Setup cancel handlers + // Setup cancel handlers - __block int socketFDRefCount = 2; + __block int socketFDRefCount = 2; - int theSocketFD = socket6FD; + int theSocketFD = socket6FD; - #if !OS_OBJECT_USE_OBJC - dispatch_source_t theSendSource = send6Source; - dispatch_source_t theReceiveSource = receive6Source; - #endif +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theSendSource = send6Source; + dispatch_source_t theReceiveSource = receive6Source; +#endif - dispatch_source_set_cancel_handler(send6Source, ^{ + dispatch_source_set_cancel_handler(send6Source, ^{ - LogVerbose(@"send6CancelBlock"); + LogVerbose(@"send6CancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(send6Source)"); - dispatch_release(theSendSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(send6Source)"); + dispatch_release(theSendSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); - dispatch_source_set_cancel_handler(receive6Source, ^{ + dispatch_source_set_cancel_handler(receive6Source, ^{ - LogVerbose(@"receive6CancelBlock"); + LogVerbose(@"receive6CancelBlock"); - #if !OS_OBJECT_USE_OBJC - LogVerbose(@"dispatch_release(receive6Source)"); - dispatch_release(theReceiveSource); - #endif +#if !OS_OBJECT_USE_OBJC + LogVerbose(@"dispatch_release(receive6Source)"); + dispatch_release(theReceiveSource); +#endif - if (--socketFDRefCount == 0) - { - LogVerbose(@"close(socket6FD)"); - close(theSocketFD); - } - }); + if (--socketFDRefCount == 0) + { + LogVerbose(@"close(socket6FD)"); + close(theSocketFD); + } + }); - // We will not be able to receive until the socket is bound to a port, - // either explicitly via bind, or implicitly by connect or by sending data. - // - // But we should be able to send immediately. + // We will not be able to receive until the socket is bound to a port, + // either explicitly via bind, or implicitly by connect or by sending data. + // + // But we should be able to send immediately. - socket6FDBytesAvailable = 0; - flags |= kSock6CanAcceptBytes; + socket6FDBytesAvailable = 0; + flags |= kSock6CanAcceptBytes; - flags |= kSend6SourceSuspended; - flags |= kReceive6SourceSuspended; + flags |= kSend6SourceSuspended; + flags |= kReceive6SourceSuspended; } - (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr { - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created"); - - // CreateSocket Block - // This block will be invoked below. - - int(^createSocket)(int) = ^int (int domain) { - - int socketFD = socket(domain, SOCK_DGRAM, 0); - - if (socketFD == SOCKET_NULL) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; - - return SOCKET_NULL; - } - - int status; - - // Set socket options - - status = fcntl(socketFD, F_SETFL, O_NONBLOCK); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int reuseaddr = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - int nosigpipe = 1; - status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; - - close(socketFD); - return SOCKET_NULL; - } - - /** - * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. - * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. - * - * The default maximum size of the UDP buffer in iOS is 9216 bytes. - * - * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and - * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) - * - * - * Enlarge the maximum size of UDP packet. - * I can not ensure the protocol type now so that the max size is set to 65535 :) - **/ - - status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; - close(socketFD); - return SOCKET_NULL; - } - - status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int)); - if (status == -1) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; - close(socketFD); - return SOCKET_NULL; - } - - - return socketFD; - }; - - // Create sockets depending upon given configuration. - - if (useIPv4) - { - LogVerbose(@"Creating IPv4 socket"); - - socket4FD = createSocket(AF_INET); - if (socket4FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - return NO; - } - } - - if (useIPv6) - { - LogVerbose(@"Creating IPv6 socket"); - - socket6FD = createSocket(AF_INET6); - if (socket6FD == SOCKET_NULL) - { - // errPtr set in local createSocket() block - - if (socket4FD != SOCKET_NULL) - { - close(socket4FD); - socket4FD = SOCKET_NULL; - } - - return NO; - } - } - - // Setup send and receive sources - - if (useIPv4) - [self setupSendAndReceiveSourcesForSocket4]; - if (useIPv6) - [self setupSendAndReceiveSourcesForSocket6]; - - flags |= kDidCreateSockets; - return YES; + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(((flags & kDidCreateSockets) == 0), + @"Sockets have already been created"); + + // CreateSocket Block + // This block will be invoked below. + + int(^createSocket)(int) = ^int (int domain) { + + int socketFD = socket(domain, SOCK_DGRAM, 0); + + if (socketFD == SOCKET_NULL) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; + + return SOCKET_NULL; + } + + int status; + + // Set socket options + + status = fcntl(socketFD, F_SETFL, O_NONBLOCK); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int reuseaddr = 1; + status = setsockopt(socketFD, + SOL_SOCKET, + SO_REUSEADDR, + &reuseaddr, + sizeof(reuseaddr)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + int nosigpipe = 1; + status = setsockopt(socketFD, + SOL_SOCKET, + SO_NOSIGPIPE, + &nosigpipe, + sizeof(nosigpipe)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; + + close(socketFD); + return SOCKET_NULL; + } + + /** + * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. + * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. + * + * The default maximum size of the UDP buffer in iOS is 9216 bytes. + * + * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and + * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) + * + * + * Enlarge the maximum size of UDP packet. + * I can not ensure the protocol type now so that the max size is set to 65535 :) + **/ + + status = setsockopt(socketFD, + SOL_SOCKET, + SO_SNDBUF, + (const char*)&self->maxSendSize, + sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + status = setsockopt(socketFD, + SOL_SOCKET, + SO_RCVBUF, + (const char*)&self->maxSendSize, + sizeof(int)); + if (status == -1) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; + close(socketFD); + return SOCKET_NULL; + } + + + return socketFD; + }; + + // Create sockets depending upon given configuration. + + if (useIPv4) + { + LogVerbose(@"Creating IPv4 socket"); + + socket4FD = createSocket(AF_INET); + if (socket4FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + return NO; + } + } + + if (useIPv6) + { + LogVerbose(@"Creating IPv6 socket"); + + socket6FD = createSocket(AF_INET6); + if (socket6FD == SOCKET_NULL) + { + // errPtr set in local createSocket() block + + if (socket4FD != SOCKET_NULL) + { + close(socket4FD); + socket4FD = SOCKET_NULL; + } + + return NO; + } + } + + // Setup send and receive sources + + if (useIPv4) + [self setupSendAndReceiveSourcesForSocket4]; + if (useIPv6) + [self setupSendAndReceiveSourcesForSocket6]; + + flags |= kDidCreateSockets; + return YES; } - (BOOL)createSockets:(NSError **)errPtr { - LogTrace(); + LogTrace(); - BOOL useIPv4 = [self isIPv4Enabled]; - BOOL useIPv6 = [self isIPv6Enabled]; + BOOL useIPv4 = [self isIPv4Enabled]; + BOOL useIPv6 = [self isIPv6Enabled]; - return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; + return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; } - (void)suspendSend4Source { - if (send4Source && !(flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send4Source)"); + if (send4Source && !(flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send4Source)"); - dispatch_suspend(send4Source); - flags |= kSend4SourceSuspended; - } + dispatch_suspend(send4Source); + flags |= kSend4SourceSuspended; + } } - (void)suspendSend6Source { - if (send6Source && !(flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(send6Source)"); + if (send6Source && !(flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(send6Source)"); - dispatch_suspend(send6Source); - flags |= kSend6SourceSuspended; - } + dispatch_suspend(send6Source); + flags |= kSend6SourceSuspended; + } } - (void)resumeSend4Source { - if (send4Source && (flags & kSend4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send4Source)"); + if (send4Source && (flags & kSend4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send4Source)"); - dispatch_resume(send4Source); - flags &= ~kSend4SourceSuspended; - } + dispatch_resume(send4Source); + flags &= ~kSend4SourceSuspended; + } } - (void)resumeSend6Source { - if (send6Source && (flags & kSend6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(send6Source)"); + if (send6Source && (flags & kSend6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(send6Source)"); - dispatch_resume(send6Source); - flags &= ~kSend6SourceSuspended; - } + dispatch_resume(send6Source); + flags &= ~kSend6SourceSuspended; + } } - (void)suspendReceive4Source { - if (receive4Source && !(flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive4Source)"); + if (receive4Source && !(flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive4Source)"); - dispatch_suspend(receive4Source); - flags |= kReceive4SourceSuspended; - } + dispatch_suspend(receive4Source); + flags |= kReceive4SourceSuspended; + } } - (void)suspendReceive6Source { - if (receive6Source && !(flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_suspend(receive6Source)"); + if (receive6Source && !(flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_suspend(receive6Source)"); - dispatch_suspend(receive6Source); - flags |= kReceive6SourceSuspended; - } + dispatch_suspend(receive6Source); + flags |= kReceive6SourceSuspended; + } } - (void)resumeReceive4Source { - if (receive4Source && (flags & kReceive4SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive4Source)"); + if (receive4Source && (flags & kReceive4SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive4Source)"); - dispatch_resume(receive4Source); - flags &= ~kReceive4SourceSuspended; - } + dispatch_resume(receive4Source); + flags &= ~kReceive4SourceSuspended; + } } - (void)resumeReceive6Source { - if (receive6Source && (flags & kReceive6SourceSuspended)) - { - LogVerbose(@"dispatch_resume(receive6Source)"); + if (receive6Source && (flags & kReceive6SourceSuspended)) + { + LogVerbose(@"dispatch_resume(receive6Source)"); - dispatch_resume(receive6Source); - flags &= ~kReceive6SourceSuspended; - } + dispatch_resume(receive6Source); + flags &= ~kReceive6SourceSuspended; + } } - (void)closeSocket4 { - if (socket4FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send4Source)"); - dispatch_source_cancel(send4Source); + if (socket4FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send4Source)"); + dispatch_source_cancel(send4Source); - LogVerbose(@"dispatch_source_cancel(receive4Source)"); - dispatch_source_cancel(receive4Source); + LogVerbose(@"dispatch_source_cancel(receive4Source)"); + dispatch_source_cancel(receive4Source); - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - [self resumeSend4Source]; - [self resumeReceive4Source]; + [self resumeSend4Source]; + [self resumeReceive4Source]; - // The sockets will be closed by the cancel handlers of the corresponding source + // The sockets will be closed by the cancel handlers of the corresponding source - send4Source = NULL; - receive4Source = NULL; + send4Source = NULL; + receive4Source = NULL; - socket4FD = SOCKET_NULL; + socket4FD = SOCKET_NULL; - // Clear socket states + // Clear socket states - socket4FDBytesAvailable = 0; - flags &= ~kSock4CanAcceptBytes; + socket4FDBytesAvailable = 0; + flags &= ~kSock4CanAcceptBytes; - // Clear cached info + // Clear cached info - cachedLocalAddress4 = nil; - cachedLocalHost4 = nil; - cachedLocalPort4 = 0; - } + cachedLocalAddress4 = nil; + cachedLocalHost4 = nil; + cachedLocalPort4 = 0; + } } - (void)closeSocket6 { - if (socket6FD != SOCKET_NULL) - { - LogVerbose(@"dispatch_source_cancel(send6Source)"); - dispatch_source_cancel(send6Source); + if (socket6FD != SOCKET_NULL) + { + LogVerbose(@"dispatch_source_cancel(send6Source)"); + dispatch_source_cancel(send6Source); - LogVerbose(@"dispatch_source_cancel(receive6Source)"); - dispatch_source_cancel(receive6Source); + LogVerbose(@"dispatch_source_cancel(receive6Source)"); + dispatch_source_cancel(receive6Source); - // For some crazy reason (in my opinion), cancelling a dispatch source doesn't - // invoke the cancel handler if the dispatch source is paused. - // So we have to unpause the source if needed. - // This allows the cancel handler to be run, which in turn releases the source and closes the socket. + // For some crazy reason (in my opinion), cancelling a dispatch source doesn't + // invoke the cancel handler if the dispatch source is paused. + // So we have to unpause the source if needed. + // This allows the cancel handler to be run, which in turn releases the source and closes the socket. - [self resumeSend6Source]; - [self resumeReceive6Source]; + [self resumeSend6Source]; + [self resumeReceive6Source]; - send6Source = NULL; - receive6Source = NULL; + send6Source = NULL; + receive6Source = NULL; - // The sockets will be closed by the cancel handlers of the corresponding source + // The sockets will be closed by the cancel handlers of the corresponding source - socket6FD = SOCKET_NULL; + socket6FD = SOCKET_NULL; - // Clear socket states + // Clear socket states - socket6FDBytesAvailable = 0; - flags &= ~kSock6CanAcceptBytes; + socket6FDBytesAvailable = 0; + flags &= ~kSock6CanAcceptBytes; - // Clear cached info + // Clear cached info - cachedLocalAddress6 = nil; - cachedLocalHost6 = nil; - cachedLocalPort6 = 0; - } + cachedLocalAddress6 = nil; + cachedLocalHost6 = nil; + cachedLocalPort6 = 0; + } } - (void)closeSockets { - [self closeSocket4]; - [self closeSocket6]; + [self closeSocket4]; + [self closeSocket6]; - flags &= ~kDidCreateSockets; + flags &= ~kDidCreateSockets; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2306,470 +2381,481 @@ - (BOOL)getLocalAddress:(NSData **)dataPtr withFamily:(int)socketFamily { - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - - if (socketFamily == AF_INET) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - else if (socketFamily == AF_INET6) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - } - else - { - LogWarn(@"Error in getsockname: %@", [self errnoError]); - } - } - - if (dataPtr) *dataPtr = data; - if (hostPtr) *hostPtr = host; - if (portPtr) *portPtr = port; - - return (data != nil); + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + + if (socketFamily == AF_INET) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getsockname(socketFD, + (struct sockaddr *)&sockaddr4, + &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + else if (socketFamily == AF_INET6) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getsockname(socketFD, + (struct sockaddr *)&sockaddr6, + &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + } + else + { + LogWarn(@"Error in getsockname: %@", [self errnoError]); + } + } + + if (dataPtr) *dataPtr = data; + if (hostPtr) *hostPtr = host; + if (portPtr) *portPtr = port; + + return (data != nil); } - (void)maybeUpdateCachedLocalAddress4Info { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) - { - return; - } + if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) + { + return; + } - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) - { + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) + { - cachedLocalAddress4 = address; - cachedLocalHost4 = host; - cachedLocalPort4 = port; - } + cachedLocalAddress4 = address; + cachedLocalHost4 = host; + cachedLocalPort4 = port; + } } - (void)maybeUpdateCachedLocalAddress6Info { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) - { - return; - } + if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) + { + return; + } - NSData *address = nil; - NSString *host = nil; - uint16_t port = 0; + NSData *address = nil; + NSString *host = nil; + uint16_t port = 0; - if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) - { + if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) + { - cachedLocalAddress6 = address; - cachedLocalHost6 = host; - cachedLocalPort6 = port; - } + cachedLocalAddress6 = address; + cachedLocalHost6 = host; + cachedLocalPort6 = port; + } } - (NSData *)localAddress { - __block NSData *result = nil; + __block NSData *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalAddress4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalAddress6; - } + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + } - }; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSString *)localHost { - __block NSString *result = nil; + __block NSString *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalHost4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalHost6; - } - }; + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (uint16_t)localPort { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->socket4FD != SOCKET_NULL) - { - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalPort4; - } - else - { - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalPort6; - } - }; + if (self->socket4FD != SOCKET_NULL) + { + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + } + else + { + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSData *)localAddress_IPv4 { - __block NSData *result = nil; + __block NSData *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalAddress4; - }; + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalAddress4; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSString *)localHost_IPv4 { - __block NSString *result = nil; + __block NSString *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalHost4; - }; + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalHost4; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (uint16_t)localPort_IPv4 { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress4Info]; - result = self->cachedLocalPort4; - }; + [self maybeUpdateCachedLocalAddress4Info]; + result = self->cachedLocalPort4; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSData *)localAddress_IPv6 { - __block NSData *result = nil; + __block NSData *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalAddress6; - }; + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalAddress6; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSString *)localHost_IPv6 { - __block NSString *result = nil; + __block NSString *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalHost6; - }; + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalHost6; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (uint16_t)localPort_IPv6 { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedLocalAddress6Info]; - result = self->cachedLocalPort6; - }; + [self maybeUpdateCachedLocalAddress6Info]; + result = self->cachedLocalPort6; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (void)maybeUpdateCachedConnectedAddressInfo { - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - if (cachedConnectedAddress || (flags & kDidConnect) == 0) - { - return; - } - - NSData *data = nil; - NSString *host = nil; - uint16_t port = 0; - int family = AF_UNSPEC; - - if (socket4FD != SOCKET_NULL) - { - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) - { - data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - host = [[self class] hostFromSockaddr4:&sockaddr4]; - port = [[self class] portFromSockaddr4:&sockaddr4]; - family = AF_INET; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - else if (socket6FD != SOCKET_NULL) - { - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) - { - data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - host = [[self class] hostFromSockaddr6:&sockaddr6]; - port = [[self class] portFromSockaddr6:&sockaddr6]; - family = AF_INET6; - } - else - { - LogWarn(@"Error in getpeername: %@", [self errnoError]); - } - } - - - cachedConnectedAddress = data; - cachedConnectedHost = host; - cachedConnectedPort = port; - cachedConnectedFamily = family; + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + if (cachedConnectedAddress || (flags & kDidConnect) == 0) + { + return; + } + + NSData *data = nil; + NSString *host = nil; + uint16_t port = 0; + int family = AF_UNSPEC; + + if (socket4FD != SOCKET_NULL) + { + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + if (getpeername(socket4FD, + (struct sockaddr *)&sockaddr4, + &sockaddr4len) == 0) + { + data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + host = [[self class] hostFromSockaddr4:&sockaddr4]; + port = [[self class] portFromSockaddr4:&sockaddr4]; + family = AF_INET; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + else if (socket6FD != SOCKET_NULL) + { + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + if (getpeername(socket6FD, + (struct sockaddr *)&sockaddr6, + &sockaddr6len) == 0) + { + data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + host = [[self class] hostFromSockaddr6:&sockaddr6]; + port = [[self class] portFromSockaddr6:&sockaddr6]; + family = AF_INET6; + } + else + { + LogWarn(@"Error in getpeername: %@", [self errnoError]); + } + } + + + cachedConnectedAddress = data; + cachedConnectedHost = host; + cachedConnectedPort = port; + cachedConnectedFamily = family; } - (NSData *)connectedAddress { - __block NSData *result = nil; + __block NSData *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedAddress; - }; + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedAddress; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (NSString *)connectedHost { - __block NSString *result = nil; + __block NSString *result = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedHost; - }; + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedHost; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (uint16_t)connectedPort { - __block uint16_t result = 0; + __block uint16_t result = 0; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - [self maybeUpdateCachedConnectedAddressInfo]; - result = self->cachedConnectedPort; - }; + [self maybeUpdateCachedConnectedAddressInfo]; + result = self->cachedConnectedPort; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, AutoreleasedBlock(block)); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, AutoreleasedBlock(block)); - return result; + return result; } - (BOOL)isConnected { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ - result = (self->flags & kDidConnect) ? YES : NO; - }; + dispatch_block_t block = ^{ + result = (self->flags & kDidConnect) ? YES : NO; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isClosed { - __block BOOL result = YES; + __block BOOL result = YES; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - result = (self->flags & kDidCreateSockets) ? NO : YES; - }; + result = (self->flags & kDidCreateSockets) ? NO : YES; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isIPv4 { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->flags & kDidCreateSockets) - { - result = (self->socket4FD != SOCKET_NULL); - } - else - { - result = [self isIPv4Enabled]; - } - }; + if (self->flags & kDidCreateSockets) + { + result = (self->socket4FD != SOCKET_NULL); + } + else + { + result = [self isIPv4Enabled]; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } - (BOOL)isIPv6 { - __block BOOL result = NO; + __block BOOL result = NO; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if (self->flags & kDidCreateSockets) - { - result = (self->socket6FD != SOCKET_NULL); - } - else - { - result = [self isIPv6Enabled]; - } - }; + if (self->flags & kDidCreateSockets) + { + result = (self->socket6FD != SOCKET_NULL); + } + else + { + result = [self isIPv6Enabled]; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - return result; + return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2779,296 +2865,301 @@ - (BOOL)isIPv6 /** * This method runs through the various checks required prior to a bind attempt. * It is shared between the various bind methods. -**/ + **/ - (BOOL)preBind:(NSError **)errPtr { - if (![self preOp:errPtr]) - { - return NO; - } - - if (flags & kDidBind) - { - if (errPtr) - { - NSString *msg = @"Cannot bind a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; + if (![self preOp:errPtr]) + { + return NO; + } + + if (flags & kDidBind) + { + if (errPtr) + { + NSString *msg = @"Cannot bind a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; } - (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr { - return [self bindToPort:port interface:nil error:errPtr]; + return [self bindToPort:port interface:nil error:errPtr]; } - (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Run through sanity checks + // Run through sanity checks - if (![self preBind:&err]) - { - return_from_block; - } + if (![self preBind:&err]) + { + return_from_block; + } - // Check the given interface + // Check the given interface - NSData *interface4 = nil; - NSData *interface6 = nil; + NSData *interface4 = nil; + NSData *interface6 = nil; - [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; + [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; - if ((interface4 == nil) && (interface6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; + if ((interface4 == nil) && (interface6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - if (isIPv4Disabled && (interface6 == nil)) - { - NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; - err = [self badParamError:msg]; + if (isIPv4Disabled && (interface6 == nil)) + { + NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - if (isIPv6Disabled && (interface4 == nil)) - { - NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; - err = [self badParamError:msg]; + if (isIPv6Disabled && (interface4 == nil)) + { + NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Determine protocol(s) + // Determine protocol(s) - BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); + BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); - // Create the socket(s) if needed + // Create the socket(s) if needed - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } - // Bind the socket(s) + // Bind the socket(s) - LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); + LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); - if (useIPv4) - { - int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); - if (status == -1) - { - [self closeSockets]; + if (useIPv4) + { + int status = bind(self->socket4FD, + (const struct sockaddr *)[interface4 bytes], + (socklen_t)[interface4 length]); + if (status == -1) + { + [self closeSockets]; - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; - return_from_block; - } - } + return_from_block; + } + } - if (useIPv6) - { - int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); - if (status == -1) - { - [self closeSockets]; + if (useIPv6) + { + int status = bind(self->socket6FD, + (const struct sockaddr *)[interface6 bytes], + (socklen_t)[interface6 length]); + if (status == -1) + { + [self closeSockets]; - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; - return_from_block; - } - } + return_from_block; + } + } - // Update flags + // Update flags - self->flags |= kDidBind; + self->flags |= kDidBind; - if (!useIPv4) self->flags |= kIPv4Deactivated; - if (!useIPv6) self->flags |= kIPv6Deactivated; + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; - result = YES; + result = YES; - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error binding to port/interface: %@", err); + if (err) + LogError(@"Error binding to port/interface: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Run through sanity checks + // Run through sanity checks - if (![self preBind:&err]) - { - return_from_block; - } + if (![self preBind:&err]) + { + return_from_block; + } - // Check the given address + // Check the given address - int addressFamily = [[self class] familyFromAddress:localAddr]; + int addressFamily = [[self class] familyFromAddress:localAddr]; - if (addressFamily == AF_UNSPEC) - { - NSString *msg = @"A valid IPv4 or IPv6 address was not given"; - err = [self badParamError:msg]; + if (addressFamily == AF_UNSPEC) + { + NSString *msg = @"A valid IPv4 or IPv6 address was not given"; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; - NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; + NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; + NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; - BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; + BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO; - if (isIPv4Disabled && localAddr4) - { - NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; - err = [self badParamError:msg]; + if (isIPv4Disabled && localAddr4) + { + NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - if (isIPv6Disabled && localAddr6) - { - NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; - err = [self badParamError:msg]; + if (isIPv6Disabled && localAddr6) + { + NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Determine protocol(s) + // Determine protocol(s) - BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); - BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); + BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); + BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); - // Create the socket(s) if needed + // Create the socket(s) if needed - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) + { + return_from_block; + } + } - // Bind the socket(s) + // Bind the socket(s) - if (useIPv4 || useIPv6) - { - NSData *addressData = useIPv4 ? localAddr4 : localAddr6; - int socketFD = useIPv4 ? self->socket4FD : self->socket6FD; - NSString *protocol = useIPv4 ? @"IPv4" : @"IPv6"; + if (useIPv4 || useIPv6) + { + NSData *addressData = useIPv4 ? localAddr4 : localAddr6; + int socketFD = useIPv4 ? self->socket4FD : self->socket6FD; + NSString *protocol = useIPv4 ? @"IPv4" : @"IPv6"; - LogVerbose(@"Binding socket to address(%@:%hu)", - [[self class] hostFromAddress:addressData], - [[self class] portFromAddress:addressData]); + LogVerbose(@"Binding socket to address(%@:%hu)", + [[self class] hostFromAddress:addressData], + [[self class] portFromAddress:addressData]); - const struct sockaddr *addr = (const struct sockaddr *)[addressData bytes]; - if (addr == NULL) - { - [self closeSockets]; + const struct sockaddr *addr = (const struct sockaddr *)[addressData bytes]; + if (addr == NULL) + { + [self closeSockets]; - NSString *reason = [NSString stringWithFormat:@"Invalid address data for %@ bind", protocol]; - err = [self badParamError:reason]; + NSString *reason = [NSString stringWithFormat:@"Invalid address data for %@ bind", + protocol]; + err = [self badParamError:reason]; - return_from_block; - } + return_from_block; + } - int status = bind(socketFD, addr, (socklen_t)[addressData length]); - if (status == -1) - { - [self closeSockets]; + int status = bind(socketFD, addr, (socklen_t)[addressData length]); + if (status == -1) + { + [self closeSockets]; - NSString *reason = @"Error in bind() function"; - err = [self errnoErrorWithReason:reason]; + NSString *reason = @"Error in bind() function"; + err = [self errnoErrorWithReason:reason]; - return_from_block; - } - } + return_from_block; + } + } - // Update flags + // Update flags - self->flags |= kDidBind; + self->flags |= kDidBind; - if (!useIPv4) self->flags |= kIPv4Deactivated; - if (!useIPv6) self->flags |= kIPv6Deactivated; + if (!useIPv4) self->flags |= kIPv4Deactivated; + if (!useIPv6) self->flags |= kIPv6Deactivated; - result = YES; + result = YES; - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error binding to address: %@", err); + if (err) + LogError(@"Error binding to address: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3078,292 +3169,300 @@ - (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr /** * This method runs through the various checks required prior to a connect attempt. * It is shared between the various connect methods. -**/ + **/ - (BOOL)preConnect:(NSError **)errPtr { - if (![self preOp:errPtr]) - { - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot connect a socket more than once."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; - BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; - - if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled - { - if (errPtr) - { - NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; + if (![self preOp:errPtr]) + { + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot connect a socket more than once."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; + BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; + + if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled + { + if (errPtr) + { + NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; } - (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Run through sanity checks. + // Run through sanity checks. - if (![self preConnect:&err]) - { - return_from_block; - } + if (![self preConnect:&err]) + { + return_from_block; + } - // Check parameter(s) + // Check parameter(s) - if (host == nil) - { - NSString *msg = @"The host param is nil. Should be domain name or IP address string."; - err = [self badParamError:msg]; + if (host == nil) + { + NSString *msg = @"The host param is nil. Should be domain name or IP address string."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Create the socket(s) if needed + // Create the socket(s) if needed - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - // Create special connect packet + // Create special connect packet - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->resolveInProgress = YES; + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->resolveInProgress = YES; - // Start asynchronous DNS resolve for host:port on background queue + // Start asynchronous DNS resolve for host:port on background queue - LogVerbose(@"Dispatching DNS resolve for connect..."); + LogVerbose(@"Dispatching DNS resolve for connect..."); - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, + NSError *error) { - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. - packet->resolveInProgress = NO; + packet->resolveInProgress = NO; - packet->addresses = addresses; - packet->error = error; + packet->addresses = addresses; + packet->error = error; - [self maybeConnect]; - }]; + [self maybeConnect]; + }]; - // Updates flags, add connect packet to send queue, and pump send queue + // Updates flags, add connect packet to send queue, and pump send queue - self->flags |= kConnecting; + self->flags |= kConnecting; - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error connecting to host/port: %@", err); + if (err) + LogError(@"Error connecting to host/port: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Run through sanity checks. + // Run through sanity checks. - if (![self preConnect:&err]) - { - return_from_block; - } + if (![self preConnect:&err]) + { + return_from_block; + } - // Check parameter(s) + // Check parameter(s) - if (remoteAddr == nil) - { - NSString *msg = @"The address param is nil. Should be a valid address."; - err = [self badParamError:msg]; + if (remoteAddr == nil) + { + NSString *msg = @"The address param is nil. Should be a valid address."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Create the socket(s) if needed + // Create the socket(s) if needed - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - // The remoteAddr parameter could be of type NSMutableData. - // So we copy it to be safe. + // The remoteAddr parameter could be of type NSMutableData. + // So we copy it to be safe. - NSData *address = [remoteAddr copy]; - NSArray *addresses = [NSArray arrayWithObject:address]; + NSData *address = [remoteAddr copy]; + NSArray *addresses = [NSArray arrayWithObject:address]; - GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; - packet->addresses = addresses; + GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; + packet->addresses = addresses; - // Updates flags, add connect packet to send queue, and pump send queue + // Updates flags, add connect packet to send queue, and pump send queue - self->flags |= kConnecting; + self->flags |= kConnecting; - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; - result = YES; - }}; + result = YES; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error connecting to address: %@", err); + if (err) + LogError(@"Error connecting to address: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (void)maybeConnect { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; + BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; - if (sendQueueReady) - { - GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; + if (sendQueueReady) + { + GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; - if (connectPacket->resolveInProgress) - { - LogVerbose(@"Waiting for DNS resolve..."); - } - else - { - if (connectPacket->error) - { - [self notifyDidNotConnect:connectPacket->error]; - } - else - { - NSData *address = nil; - NSError *error = nil; + if (connectPacket->resolveInProgress) + { + LogVerbose(@"Waiting for DNS resolve..."); + } + else + { + if (connectPacket->error) + { + [self notifyDidNotConnect:connectPacket->error]; + } + else + { + NSData *address = nil; + NSError *error = nil; - int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; + int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; - // Perform connect + // Perform connect - BOOL result = NO; + BOOL result = NO; - switch (addressFamily) - { - case AF_INET : result = [self connectWithAddress4:address error:&error]; break; - case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; + switch (addressFamily) + { + case AF_INET : result = [self connectWithAddress4:address error:&error]; break; + case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; default: break; - } + } - if (result) - { - flags |= kDidBind; - flags |= kDidConnect; + if (result) + { + flags |= kDidBind; + flags |= kDidConnect; - cachedConnectedAddress = address; - cachedConnectedHost = [[self class] hostFromAddress:address]; - cachedConnectedPort = [[self class] portFromAddress:address]; - cachedConnectedFamily = addressFamily; + cachedConnectedAddress = address; + cachedConnectedHost = [[self class] hostFromAddress:address]; + cachedConnectedPort = [[self class] portFromAddress:address]; + cachedConnectedFamily = addressFamily; - [self notifyDidConnectToAddress:address]; - } - else - { - [self notifyDidNotConnect:error]; - } - } + [self notifyDidConnectToAddress:address]; + } + else + { + [self notifyDidNotConnect:error]; + } + } - flags &= ~kConnecting; + flags &= ~kConnecting; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } } - (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + int status = connect(socket4FD, + (const struct sockaddr *)[address4 bytes], + (socklen_t)[address4 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - return NO; - } + return NO; + } - [self closeSocket6]; - flags |= kIPv6Deactivated; + [self closeSocket6]; + flags |= kIPv6Deactivated; - return YES; + return YES; } - (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); - if (status != 0) - { - if (errPtr) - *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; + int status = connect(socket6FD, + (const struct sockaddr *)[address6 bytes], + (socklen_t)[address6 length]); + if (status != 0) + { + if (errPtr) + *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; - return NO; - } + return NO; + } - [self closeSocket4]; - flags |= kIPv4Deactivated; + [self closeSocket4]; + flags |= kIPv4Deactivated; - return YES; + return YES; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3372,54 +3471,54 @@ - (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr - (BOOL)preJoin:(NSError **)errPtr { - if (![self preOp:errPtr]) - { - return NO; - } - - if (!(flags & kDidBind)) - { - if (errPtr) - { - NSString *msg = @"Must bind a socket before joining a multicast group."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - if ((flags & kConnecting) || (flags & kDidConnect)) - { - if (errPtr) - { - NSString *msg = @"Cannot join a multicast group if connected."; - *errPtr = [self badConfigError:msg]; - } - return NO; - } - - return YES; + if (![self preOp:errPtr]) + { + return NO; + } + + if (!(flags & kDidBind)) + { + if (errPtr) + { + NSString *msg = @"Must bind a socket before joining a multicast group."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + if ((flags & kConnecting) || (flags & kDidConnect)) + { + if (errPtr) + { + NSString *msg = @"Cannot join a multicast group if connected."; + *errPtr = [self badConfigError:msg]; + } + return NO; + } + + return YES; } - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr { - return [self joinMulticastGroup:group onInterface:nil error:errPtr]; + return [self joinMulticastGroup:group onInterface:nil error:errPtr]; } - (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr { - // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP - return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; + // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP + return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; } - (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr { - return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; + return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; } - (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr { - // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP - return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; + // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP + return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; } - (BOOL)performMulticastRequest:(int)requestType @@ -3427,227 +3526,243 @@ - (BOOL)performMulticastRequest:(int)requestType onInterface:(NSString *)interface error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - // Run through sanity checks + // Run through sanity checks - if (![self preJoin:&err]) - { - return_from_block; - } + if (![self preJoin:&err]) + { + return_from_block; + } - // Convert group to address + // Convert group to address - NSData *groupAddr4 = nil; - NSData *groupAddr6 = nil; + NSData *groupAddr4 = nil; + NSData *groupAddr6 = nil; - [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; + [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; - if ((groupAddr4 == nil) && (groupAddr6 == nil)) - { - NSString *msg = @"Unknown group. Specify valid group IP address."; - err = [self badParamError:msg]; + if ((groupAddr4 == nil) && (groupAddr6 == nil)) + { + NSString *msg = @"Unknown group. Specify valid group IP address."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Convert interface to address + // Convert interface to address - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; - err = [self badParamError:msg]; + if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - // Perform join + // Perform join if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) - { - const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; - const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; - - struct ip_mreq imreq; - imreq.imr_multiaddr = nativeGroup->sin_addr; - imreq.imr_interface = nativeIface->sin_addr; - - int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv4 only - [self closeSocket6]; - - result = YES; - } + { + const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes]; + const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes]; + + struct ip_mreq imreq; + imreq.imr_multiaddr = nativeGroup->sin_addr; + imreq.imr_interface = nativeIface->sin_addr; + + int status = setsockopt(self->socket4FD, + IPPROTO_IP, + requestType, + (const void *)&imreq, + sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv4 only + [self closeSocket6]; + + result = YES; + } else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) - { - const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; - - struct ipv6_mreq imreq; - imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; - imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; - - int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq)); - if (status != 0) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - - return_from_block; - } - - // Using IPv6 only - [self closeSocket4]; - - result = YES; - } - else - { - NSString *msg = @"Socket, group, and interface do not have matching IP versions"; - err = [self badParamError:msg]; + { + const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes]; + + struct ipv6_mreq imreq; + imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; + imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; + + int status = setsockopt(self->socket6FD, + IPPROTO_IPV6, + requestType, + (const void *)&imreq, + sizeof(imreq)); + if (status != 0) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + + return_from_block; + } + + // Using IPv6 only + [self closeSocket4]; + + result = YES; + } + else + { + NSString *msg = @"Socket, group, and interface do not have matching IP versions"; + err = [self badParamError:msg]; - return_from_block; - } + return_from_block; + } - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (![self preOp:&err]) - { - return_from_block; - } + if (![self preOp:&err]) + { + return_from_block; + } - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - // Convert interface to address + // Convert interface to address - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - if (interfaceAddr4 == nil) - { - NSString *msg = @"Unknown interface. Specify valid interface by IP address."; - err = [self badParamError:msg]; - return_from_block; - } + if (interfaceAddr4 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by IP address."; + err = [self badParamError:msg]; + return_from_block; + } - if (self->socket4FD != SOCKET_NULL) { - const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; - struct in_addr interface_addr = nativeIface->sin_addr; - int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)); - if (status != 0) { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - result = YES; - } - } + if (self->socket4FD != SOCKET_NULL) { + const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; + struct in_addr interface_addr = nativeIface->sin_addr; + int status = setsockopt(self->socket4FD, + IPPROTO_IP, + IP_MULTICAST_IF, + &interface_addr, + sizeof(interface_addr)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + result = YES; + } + } - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (![self preOp:&err]) - { - return_from_block; - } + if (![self preOp:&err]) + { + return_from_block; + } - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - // Convert interface to address + // Convert interface to address - NSData *interfaceAddr4 = nil; - NSData *interfaceAddr6 = nil; + NSData *interfaceAddr4 = nil; + NSData *interfaceAddr6 = nil; - [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; + [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; - if (interfaceAddr6 == nil) - { - NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; - err = [self badParamError:msg]; - return_from_block; - } + if (interfaceAddr6 == nil) + { + NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\")."; + err = [self badParamError:msg]; + return_from_block; + } - if ((self->socket6FD != SOCKET_NULL)) { - uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; - int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id)); - if (status != 0) { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - } - result = YES; - } + if ((self->socket6FD != SOCKET_NULL)) { + uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6]; + int status = setsockopt(self->socket6FD, + IPPROTO_IPV6, + IPV6_MULTICAST_IF, + &scope_id, + sizeof(scope_id)); + if (status != 0) { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + return_from_block; + } + result = YES; + } - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3656,62 +3771,70 @@ - (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errP - (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (![self preOp:&err]) - { - return_from_block; - } + if (![self preOp:&err]) + { + return_from_block; + } - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - int value = flag ? 1 : 0; + int value = flag ? 1 : 0; if (self->socket4FD != SOCKET_NULL) - { - int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + { + int error = setsockopt(self->socket4FD, + SOL_SOCKET, + SO_REUSEPORT, + (const void *)&value, + sizeof(value)); - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - } - result = YES; - } + return_from_block; + } + result = YES; + } - if (self->socket6FD != SOCKET_NULL) - { - int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); + if (self->socket6FD != SOCKET_NULL) + { + int error = setsockopt(self->socket6FD, + SOL_SOCKET, + SO_REUSEPORT, + (const void *)&value, + sizeof(value)); - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - } - result = YES; - } + return_from_block; + } + result = YES; + } - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3720,52 +3843,56 @@ - (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr { - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - if (![self preOp:&err]) - { - return_from_block; - } + if (![self preOp:&err]) + { + return_from_block; + } - if ((self->flags & kDidCreateSockets) == 0) - { - if (![self createSockets:&err]) - { - return_from_block; - } - } + if ((self->flags & kDidCreateSockets) == 0) + { + if (![self createSockets:&err]) + { + return_from_block; + } + } - if (self->socket4FD != SOCKET_NULL) - { - int value = flag ? 1 : 0; - int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); + if (self->socket4FD != SOCKET_NULL) + { + int value = flag ? 1 : 0; + int error = setsockopt(self->socket4FD, + SOL_SOCKET, + SO_BROADCAST, + (const void *)&value, + sizeof(value)); - if (error) - { - err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; + if (error) + { + err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; - return_from_block; - } - result = YES; - } + return_from_block; + } + result = YES; + } - // 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. + // 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. - }}; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -3774,28 +3901,28 @@ - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr - (void)sendData:(NSData *)data withTag:(long)tag { - [self sendData:data withTimeout:-1.0 tag:tag]; + [self sendData:data withTimeout:-1.0 tag:tag]; } - (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag { - LogTrace(); + LogTrace(); - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, ^{ @autoreleasepool { - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - }}); + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); } @@ -3805,160 +3932,163 @@ - (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag { - LogTrace(); + LogTrace(); - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->resolveInProgress = YES; + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->resolveInProgress = YES; - [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { + [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, + NSError *error) { - // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, - // and immediately returns. Once the async resolve task completes, - // this block is executed on our socketQueue. + // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, + // and immediately returns. Once the async resolve task completes, + // this block is executed on our socketQueue. - packet->resolveInProgress = NO; + packet->resolveInProgress = NO; - packet->resolvedAddresses = addresses; - packet->resolveError = error; + packet->resolvedAddresses = addresses; + packet->resolveError = error; - if (packet == self->currentSend) - { - LogVerbose(@"currentSend - address resolved"); - [self doPreSend]; - } - }]; + if (packet == self->currentSend) + { + LogVerbose(@"currentSend - address resolved"); + [self doPreSend]; + } + }]; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, ^{ @autoreleasepool { - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; - }}); + }}); } - (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag { - LogTrace(); + LogTrace(); - if ([data length] == 0) - { - LogWarn(@"Ignoring attempt to send nil/empty data."); - return; - } + if ([data length] == 0) + { + LogWarn(@"Ignoring attempt to send nil/empty data."); + return; + } - GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; - packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; - packet->address = remoteAddr; + GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; + packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; + packet->address = remoteAddr; - dispatch_async(socketQueue, ^{ @autoreleasepool { + dispatch_async(socketQueue, ^{ @autoreleasepool { - [self->sendQueue addObject:packet]; - [self maybeDequeueSend]; - }}); + [self->sendQueue addObject:packet]; + [self maybeDequeueSend]; + }}); } - (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue { - [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; + [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; } - (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous { - GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; + GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; - if (filterBlock) - { - NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + if (filterBlock) + { + NSAssert(filterQueue, + @"Must provide a dispatch_queue in which to run the filter block."); - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); - #endif - } + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); +#endif + } - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - #if !OS_OBJECT_USE_OBJC - if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue); +#endif - self->sendFilterBlock = newFilterBlock; - self->sendFilterQueue = newFilterQueue; - self->sendFilterAsync = isAsynchronous; - }; + self->sendFilterBlock = newFilterBlock; + self->sendFilterQueue = newFilterQueue; + self->sendFilterAsync = isAsynchronous; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)maybeDequeueSend { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - - // If we don't have a send operation already in progress - if (currentSend == nil) - { - // Create the sockets if needed - if ((flags & kDidCreateSockets) == 0) - { - NSError *err = nil; - if (![self createSockets:&err]) - { - [self closeWithError:err]; - return; - } - } - - while ([sendQueue count] > 0) - { - // Dequeue the next object in the queue - currentSend = [sendQueue objectAtIndex:0]; - [sendQueue removeObjectAtIndex:0]; - - if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) - { - [self maybeConnect]; - - return; // The maybeConnect method, if it connects, will invoke this method again - } - else if (currentSend->resolveError) - { - // Notify delegate - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; - - // Clear currentSend - currentSend = nil; - - continue; - } - else - { - // Start preprocessing checks on the send packet - [self doPreSend]; - - break; - } - } - - if ((currentSend == nil) && (flags & kCloseAfterSends)) - { - [self closeWithError:nil]; - } - } + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + // If we don't have a send operation already in progress + if (currentSend == nil) + { + // Create the sockets if needed + if ((flags & kDidCreateSockets) == 0) + { + NSError *err = nil; + if (![self createSockets:&err]) + { + [self closeWithError:err]; + return; + } + } + + while ([sendQueue count] > 0) + { + // Dequeue the next object in the queue + currentSend = [sendQueue objectAtIndex:0]; + [sendQueue removeObjectAtIndex:0]; + + if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) + { + [self maybeConnect]; + + return; // The maybeConnect method, if it connects, will invoke this method again + } + else if (currentSend->resolveError) + { + // Notify delegate + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; + + // Clear currentSend + currentSend = nil; + + continue; + } + else + { + // Start preprocessing checks on the send packet + [self doPreSend]; + + break; + } + } + + if ((currentSend == nil) && (flags & kCloseAfterSends)) + { + [self closeWithError:nil]; + } + } } /** @@ -3967,337 +4097,347 @@ - (void)maybeDequeueSend * and queries the sendFilter (if set) to determine if the packet can be sent. * * If the packet passes all checks, it will be passed on to the doSend method. -**/ + **/ - (void)doPreSend { - LogTrace(); - - // - // 1. Check for problems with send packet - // - - BOOL waitingForResolve = NO; - NSError *error = nil; - - if (flags & kDidConnect) - { - // Connected socket - - if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) - { - NSString *msg = @"Cannot specify destination of packet for connected socket"; - error = [self badConfigError:msg]; - } - else - { - currentSend->address = cachedConnectedAddress; - currentSend->addressFamily = cachedConnectedFamily; - } - } - else - { - // Non-Connected socket - - if (currentSend->resolveInProgress) - { - // We're waiting for the packet's destination to be resolved. - waitingForResolve = YES; - } - else if (currentSend->resolveError) - { - error = currentSend->resolveError; - } - else if (currentSend->address == nil) - { - if (currentSend->resolvedAddresses == nil) - { - NSString *msg = @"You must specify destination of packet for a non-connected socket"; - error = [self badConfigError:msg]; - } - else - { - // Pick the proper address to use (out of possibly several resolved addresses) - - NSData *address = nil; - int addressFamily = AF_UNSPEC; - - addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; - - currentSend->address = address; - currentSend->addressFamily = addressFamily; - } - } - } - - if (waitingForResolve) - { - // We're waiting for the packet's destination to be resolved. - - LogVerbose(@"currentSend - waiting for address resolve"); - - if (flags & kSock4CanAcceptBytes) { - [self suspendSend4Source]; - } - if (flags & kSock6CanAcceptBytes) { - [self suspendSend6Source]; - } - - return; - } - - if (error) - { - // Unable to send packet due to some error. - // Notify delegate and move on. - - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; - [self endCurrentSend]; - [self maybeDequeueSend]; - - return; - } - - // - // 2. Query sendFilter (if applicable) - // - - if (sendFilterBlock && sendFilterQueue) - { - // Query sendFilter - - if (sendFilterAsync) - { - // Scenario 1 of 3 - Need to asynchronously query sendFilter - - currentSend->filterInProgress = YES; - GCDAsyncUdpSendPacket *sendPacket = currentSend; - - dispatch_async(sendFilterQueue, ^{ @autoreleasepool { - - BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); - - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - sendPacket->filterInProgress = NO; - if (sendPacket == self->currentSend) - { - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:self->currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - }}); - }}); - } - else - { - // Scenario 2 of 3 - Need to synchronously query sendFilter - - __block BOOL allowed = YES; - - dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { - - allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag); - }}); - - if (allowed) - { - [self doSend]; - } - else - { - LogVerbose(@"currentSend - silently dropped by sendFilter"); - - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } - } - } - else // if (!sendFilterBlock || !sendFilterQueue) - { - // Scenario 3 of 3 - No sendFilter. Just go straight into sending. - - [self doSend]; - } + LogTrace(); + + // + // 1. Check for problems with send packet + // + + BOOL waitingForResolve = NO; + NSError *error = nil; + + if (flags & kDidConnect) + { + // Connected socket + + if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) + { + NSString *msg = @"Cannot specify destination of packet for connected socket"; + error = [self badConfigError:msg]; + } + else + { + currentSend->address = cachedConnectedAddress; + currentSend->addressFamily = cachedConnectedFamily; + } + } + else + { + // Non-Connected socket + + if (currentSend->resolveInProgress) + { + // We're waiting for the packet's destination to be resolved. + waitingForResolve = YES; + } + else if (currentSend->resolveError) + { + error = currentSend->resolveError; + } + else if (currentSend->address == nil) + { + if (currentSend->resolvedAddresses == nil) + { + NSString *msg = @"You must specify destination of packet for a non-connected socket"; + error = [self badConfigError:msg]; + } + else + { + // Pick the proper address to use (out of possibly several resolved addresses) + + NSData *address = nil; + int addressFamily = AF_UNSPEC; + + addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; + + currentSend->address = address; + currentSend->addressFamily = addressFamily; + } + } + } + + if (waitingForResolve) + { + // We're waiting for the packet's destination to be resolved. + + LogVerbose(@"currentSend - waiting for address resolve"); + + if (flags & kSock4CanAcceptBytes) { + [self suspendSend4Source]; + } + if (flags & kSock6CanAcceptBytes) { + [self suspendSend6Source]; + } + + return; + } + + if (error) + { + // Unable to send packet due to some error. + // Notify delegate and move on. + + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; + [self endCurrentSend]; + [self maybeDequeueSend]; + + return; + } + + // + // 2. Query sendFilter (if applicable) + // + + if (sendFilterBlock && sendFilterQueue) + { + // Query sendFilter + + if (sendFilterAsync) + { + // Scenario 1 of 3 - Need to asynchronously query sendFilter + + currentSend->filterInProgress = YES; + GCDAsyncUdpSendPacket *sendPacket = currentSend; + + dispatch_async(sendFilterQueue, + ^{ @autoreleasepool { + + BOOL allowed = self->sendFilterBlock(sendPacket->buffer, + sendPacket->address, + sendPacket->tag); + + dispatch_async(self->socketQueue, ^{ @autoreleasepool { + + sendPacket->filterInProgress = NO; + if (sendPacket == self->currentSend) + { + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:self->currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + }}); + }}); + } + else + { + // Scenario 2 of 3 - Need to synchronously query sendFilter + + __block BOOL allowed = YES; + + dispatch_sync(sendFilterQueue, + ^{ @autoreleasepool { + + allowed = self->sendFilterBlock(self->currentSend->buffer, + self->currentSend->address, + self->currentSend->tag); + }}); + + if (allowed) + { + [self doSend]; + } + else + { + LogVerbose(@"currentSend - silently dropped by sendFilter"); + + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } + } + } + else // if (!sendFilterBlock || !sendFilterQueue) + { + // Scenario 3 of 3 - No sendFilter. Just go straight into sending. + + [self doSend]; + } } /** * This method performs the actual sending of data in the currentSend packet. * It should only be called if the -**/ + **/ - (void)doSend { - LogTrace(); - - NSAssert(currentSend != nil, @"Invalid logic"); - - // Perform the actual send - - ssize_t result = 0; - - if (flags & kDidConnect) - { - // Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - if (currentSend->addressFamily == AF_INET) - { - result = send(socket4FD, buffer, length, 0); - LogVerbose(@"send(socket4FD) = %d", result); - } - else - { - result = send(socket6FD, buffer, length, 0); - LogVerbose(@"send(socket6FD) = %d", result); - } - } - else - { - // Non-Connected socket - - const void *buffer = [currentSend->buffer bytes]; - size_t length = (size_t)[currentSend->buffer length]; - - const void *dst = [currentSend->address bytes]; - socklen_t dstSize = (socklen_t)[currentSend->address length]; - - if (currentSend->addressFamily == AF_INET) - { - result = sendto(socket4FD, buffer, length, 0, dst, dstSize); - LogVerbose(@"sendto(socket4FD) = %d", result); - } - else - { - result = sendto(socket6FD, buffer, length, 0, dst, dstSize); - LogVerbose(@"sendto(socket6FD) = %d", result); - } - } - - // If the socket wasn't bound before, it is now - - if ((flags & kDidBind) == 0) - { - flags |= kDidBind; - } - - // Check the results. - // - // From the send() & sendto() manpage: - // - // Upon successful completion, the number of bytes which were sent is returned. - // Otherwise, -1 is returned and the global variable errno is set to indicate the error. - - BOOL waitingForSocket = NO; - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in send() function."]; - } - - if (waitingForSocket) - { - // Not enough room in the underlying OS socket send buffer. - // Wait for a notification of available space. - - LogVerbose(@"currentSend - waiting for socket"); - - if (!(flags & kSock4CanAcceptBytes)) { - [self resumeSend4Source]; - } - if (!(flags & kSock6CanAcceptBytes)) { - [self resumeSend6Source]; - } - - if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) - { - // Unable to send packet right away. - // Start timer to timeout the send operation. - - [self setupSendTimerWithTimeout:currentSend->timeout]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else // done - { - [self notifyDidSendDataWithTag:currentSend->tag]; - [self endCurrentSend]; - [self maybeDequeueSend]; - } + LogTrace(); + + NSAssert(currentSend != nil, @"Invalid logic"); + + // Perform the actual send + + ssize_t result = 0; + + if (flags & kDidConnect) + { + // Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + if (currentSend->addressFamily == AF_INET) + { + result = send(socket4FD, buffer, length, 0); + LogVerbose(@"send(socket4FD) = %d", result); + } + else + { + result = send(socket6FD, buffer, length, 0); + LogVerbose(@"send(socket6FD) = %d", result); + } + } + else + { + // Non-Connected socket + + const void *buffer = [currentSend->buffer bytes]; + size_t length = (size_t)[currentSend->buffer length]; + + const void *dst = [currentSend->address bytes]; + socklen_t dstSize = (socklen_t)[currentSend->address length]; + + if (currentSend->addressFamily == AF_INET) + { + result = sendto(socket4FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket4FD) = %d", result); + } + else + { + result = sendto(socket6FD, buffer, length, 0, dst, dstSize); + LogVerbose(@"sendto(socket6FD) = %d", result); + } + } + + // If the socket wasn't bound before, it is now + + if ((flags & kDidBind) == 0) + { + flags |= kDidBind; + } + + // Check the results. + // + // From the send() & sendto() manpage: + // + // Upon successful completion, the number of bytes which were sent is returned. + // Otherwise, -1 is returned and the global variable errno is set to indicate the error. + + BOOL waitingForSocket = NO; + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in send() function."]; + } + + if (waitingForSocket) + { + // Not enough room in the underlying OS socket send buffer. + // Wait for a notification of available space. + + LogVerbose(@"currentSend - waiting for socket"); + + if (!(flags & kSock4CanAcceptBytes)) { + [self resumeSend4Source]; + } + if (!(flags & kSock6CanAcceptBytes)) { + [self resumeSend6Source]; + } + + if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) + { + // Unable to send packet right away. + // Start timer to timeout the send operation. + + [self setupSendTimerWithTimeout:currentSend->timeout]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else // done + { + [self notifyDidSendDataWithTag:currentSend->tag]; + [self endCurrentSend]; + [self maybeDequeueSend]; + } } /** * Releases all resources associated with the currentSend. -**/ + **/ - (void)endCurrentSend { - if (sendTimer) - { - dispatch_source_cancel(sendTimer); - #if !OS_OBJECT_USE_OBJC - dispatch_release(sendTimer); - #endif - sendTimer = NULL; - } + if (sendTimer) + { + dispatch_source_cancel(sendTimer); +#if !OS_OBJECT_USE_OBJC + dispatch_release(sendTimer); +#endif + sendTimer = NULL; + } - currentSend = nil; + currentSend = nil; } /** * Performs the operations to timeout the current send operation, and move on. -**/ + **/ - (void)doSendTimeout { - LogTrace(); + LogTrace(); - [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; - [self endCurrentSend]; - [self maybeDequeueSend]; + [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; + [self endCurrentSend]; + [self maybeDequeueSend]; } /** * Sets up a timer that fires to timeout the current send operation. * This method should only be called once per send packet. -**/ + **/ - (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout { - NSAssert(sendTimer == NULL, @"Invalid logic"); - NSAssert(timeout >= 0.0, @"Invalid logic"); + NSAssert(sendTimer == NULL, @"Invalid logic"); + NSAssert(timeout >= 0.0, @"Invalid logic"); - LogTrace(); + LogTrace(); - sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); + sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + socketQueue); - dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { + dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { - [self doSendTimeout]; - }}); + [self doSendTimeout]; + }}); - dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); + dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(timeout * NSEC_PER_SEC)); - dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); - dispatch_resume(sendTimer); + dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); + dispatch_resume(sendTimer); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4306,477 +4446,490 @@ - (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout - (BOOL)receiveOnce:(NSError **)errPtr { - LogTrace(); + LogTrace(); - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if ((self->flags & kReceiveOnce) == 0) - { - if ((self->flags & kDidCreateSockets) == 0) - { - NSString *msg = @"Must bind socket before you can receive data. " - @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + if ((self->flags & kReceiveOnce) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; - err = [self badConfigError:msg]; - return_from_block; - } + err = [self badConfigError:msg]; + return_from_block; + } - self->flags |= kReceiveOnce; // Enable - self->flags &= ~kReceiveContinuous; // Disable + self->flags |= kReceiveOnce; // Enable + self->flags &= ~kReceiveContinuous; // Disable - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self doReceive]; - }}); - } + [self doReceive]; + }}); + } - result = YES; - }; + result = YES; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error in beginReceiving: %@", err); + if (err) + LogError(@"Error in beginReceiving: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (BOOL)beginReceiving:(NSError **)errPtr { - LogTrace(); + LogTrace(); - __block BOOL result = NO; - __block NSError *err = nil; + __block BOOL result = NO; + __block NSError *err = nil; - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - if ((self->flags & kReceiveContinuous) == 0) - { - if ((self->flags & kDidCreateSockets) == 0) - { - NSString *msg = @"Must bind socket before you can receive data. " - @"You can do this explicitly via bind, or implicitly via connect or by sending data."; + if ((self->flags & kReceiveContinuous) == 0) + { + if ((self->flags & kDidCreateSockets) == 0) + { + NSString *msg = @"Must bind socket before you can receive data. " + @"You can do this explicitly via bind, or implicitly via connect or by sending data."; - err = [self badConfigError:msg]; - return_from_block; - } + err = [self badConfigError:msg]; + return_from_block; + } - self->flags |= kReceiveContinuous; // Enable - self->flags &= ~kReceiveOnce; // Disable + self->flags |= kReceiveContinuous; // Enable + self->flags &= ~kReceiveOnce; // Disable - dispatch_async(self->socketQueue, ^{ @autoreleasepool { + dispatch_async(self->socketQueue, ^{ @autoreleasepool { - [self doReceive]; - }}); - } + [self doReceive]; + }}); + } - result = YES; - }; + result = YES; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); - if (err) - LogError(@"Error in beginReceiving: %@", err); + if (err) + LogError(@"Error in beginReceiving: %@", err); - if (errPtr) - *errPtr = err; + if (errPtr) + *errPtr = err; - return result; + return result; } - (void)pauseReceiving { - LogTrace(); + LogTrace(); - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - self->flags &= ~kReceiveOnce; // Disable - self->flags &= ~kReceiveContinuous; // Disable + self->flags &= ~kReceiveOnce; // Disable + self->flags &= ~kReceiveContinuous; // Disable - if (self->socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (self->socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - }; + if (self->socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (self->socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue { - [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; + [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; } - (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue isAsynchronous:(BOOL)isAsynchronous { - GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; - dispatch_queue_t newFilterQueue = NULL; + GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; + dispatch_queue_t newFilterQueue = NULL; - if (filterBlock) - { - NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); + if (filterBlock) + { + NSAssert(filterQueue, + @"Must provide a dispatch_queue in which to run the filter block."); - newFilterBlock = [filterBlock copy]; - newFilterQueue = filterQueue; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(newFilterQueue); - #endif - } + newFilterBlock = [filterBlock copy]; + newFilterQueue = filterQueue; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(newFilterQueue); +#endif + } - dispatch_block_t block = ^{ + dispatch_block_t block = ^{ - #if !OS_OBJECT_USE_OBJC - if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); - #endif +#if !OS_OBJECT_USE_OBJC + if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue); +#endif - self->receiveFilterBlock = newFilterBlock; - self->receiveFilterQueue = newFilterQueue; - self->receiveFilterAsync = isAsynchronous; - }; + self->receiveFilterBlock = newFilterBlock; + self->receiveFilterQueue = newFilterQueue; + self->receiveFilterAsync = isAsynchronous; + }; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } - (void)doReceive { - LogTrace(); - - if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) - { - LogVerbose(@"Receiving is paused..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) - { - LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); - - if (socket4FDBytesAvailable > 0) { - [self suspendReceive4Source]; - } - if (socket6FDBytesAvailable > 0) { - [self suspendReceive6Source]; - } - - return; - } - - if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) - { - LogVerbose(@"No data available to receive..."); - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - - return; - } - - // Figure out if we should receive on socket4 or socket6 - - BOOL doReceive4; - - if (flags & kDidConnect) - { - // Connected socket - - doReceive4 = (socket4FD != SOCKET_NULL); - } - else - { - // Non-Connected socket - - if (socket4FDBytesAvailable > 0) - { - if (socket6FDBytesAvailable > 0) - { - // Bytes available on socket4 & socket6 - - doReceive4 = (flags & kFlipFlop) ? YES : NO; - - flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) - } - else { - // Bytes available on socket4, but not socket6 - doReceive4 = YES; - } - } - else { - // Bytes available on socket6, but not socket4 - doReceive4 = NO; - } - } - - // Perform socket IO - - ssize_t result = 0; - - NSData *data = nil; - NSData *addr4 = nil; - NSData *addr6 = nil; - - if (doReceive4) - { - NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv4"); - - struct sockaddr_in sockaddr4; - socklen_t sockaddr4len = sizeof(sockaddr4); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max4ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); - LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket4FDBytesAvailable) - socket4FDBytesAvailable = 0; - else - socket4FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; - } - else - { - LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); - socket4FDBytesAvailable = 0; - free(buf); - } - } - else - { - NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); - LogVerbose(@"Receiving on IPv6"); - - struct sockaddr_in6 sockaddr6; - socklen_t sockaddr6len = sizeof(sockaddr6); - - // #222: GCD does not necessarily return the size of an entire UDP packet - // from dispatch_source_get_data(), so we must use the maximum packet size. - size_t bufSize = max6ReceiveSize; - void *buf = malloc(bufSize); - - result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); - LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); - - if (result > 0) - { - if ((size_t)result >= socket6FDBytesAvailable) - socket6FDBytesAvailable = 0; - else - socket6FDBytesAvailable -= result; - - if ((size_t)result != bufSize) { - buf = realloc(buf, result); - } - - data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; - addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; - } - else - { - LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); - socket6FDBytesAvailable = 0; - free(buf); - } - } - - - BOOL waitingForSocket = NO; - BOOL notifiedDelegate = NO; - BOOL ignored = NO; - - NSError *socketError = nil; - - if (result == 0) - { - waitingForSocket = YES; - } - else if (result < 0) - { - if (errno == EAGAIN) - waitingForSocket = YES; - else - socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; - } - else - { - if (flags & kDidConnect) - { - if (addr4 && ![self isConnectedToAddress4:addr4]) - ignored = YES; - if (addr6 && ![self isConnectedToAddress6:addr6]) - ignored = YES; - } - - NSData *addr = (addr4 != nil) ? addr4 : addr6; - - if (!ignored) - { - if (receiveFilterBlock && receiveFilterQueue) - { - // Run data through filter, and if approved, notify delegate - - __block id filterContext = nil; - __block BOOL allowed = NO; - - if (receiveFilterAsync) - { - pendingFilterOperations++; - dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { - - allowed = self->receiveFilterBlock(data, addr, &filterContext); - - // Transition back to socketQueue to get the current delegate / delegateQueue - dispatch_async(self->socketQueue, ^{ @autoreleasepool { - - self->pendingFilterOperations--; - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - } - - if (self->flags & kReceiveOnce) - { - if (allowed) - { - // The delegate has been notified, - // so our receive once operation has completed. - self->flags &= ~kReceiveOnce; - } - else if (self->pendingFilterOperations == 0) - { - // All pending filter operations have completed, - // and none were allowed through. - // Our receive once operation hasn't completed yet. - [self doReceive]; - } - } - }}); - }}); - } - else // if (!receiveFilterAsync) - { - dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { - - allowed = self->receiveFilterBlock(data, addr, &filterContext); - }}); - - if (allowed) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; - notifiedDelegate = YES; - } - else - { - LogVerbose(@"received packet silently dropped by receiveFilter"); - ignored = YES; - } - } - } - else // if (!receiveFilterBlock || !receiveFilterQueue) - { - [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; - notifiedDelegate = YES; - } - } - } - - if (waitingForSocket) - { - // Wait for a notification of available data. - - if (socket4FDBytesAvailable == 0) { - [self resumeReceive4Source]; - } - if (socket6FDBytesAvailable == 0) { - [self resumeReceive6Source]; - } - } - else if (socketError) - { - [self closeWithError:socketError]; - } - else - { - if (flags & kReceiveContinuous) - { - // Continuous receive mode - [self doReceive]; - } - else - { - // One-at-a-time receive mode - if (notifiedDelegate) - { - // The delegate has been notified (no set filter). - // So our receive once operation has completed. - flags &= ~kReceiveOnce; - } - else if (ignored) - { - [self doReceive]; - } - else - { - // Waiting on asynchronous receive filter... - } - } - } + LogTrace(); + + if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) + { + LogVerbose(@"Receiving is paused..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) + { + LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); + + if (socket4FDBytesAvailable > 0) { + [self suspendReceive4Source]; + } + if (socket6FDBytesAvailable > 0) { + [self suspendReceive6Source]; + } + + return; + } + + if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) + { + LogVerbose(@"No data available to receive..."); + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + + return; + } + + // Figure out if we should receive on socket4 or socket6 + + BOOL doReceive4; + + if (flags & kDidConnect) + { + // Connected socket + + doReceive4 = (socket4FD != SOCKET_NULL); + } + else + { + // Non-Connected socket + + if (socket4FDBytesAvailable > 0) + { + if (socket6FDBytesAvailable > 0) + { + // Bytes available on socket4 & socket6 + + doReceive4 = (flags & kFlipFlop) ? YES : NO; + + flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) + } + else { + // Bytes available on socket4, but not socket6 + doReceive4 = YES; + } + } + else { + // Bytes available on socket6, but not socket4 + doReceive4 = NO; + } + } + + // Perform socket IO + + ssize_t result = 0; + + NSData *data = nil; + NSData *addr4 = nil; + NSData *addr6 = nil; + + if (doReceive4) + { + NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv4"); + + struct sockaddr_in sockaddr4; + socklen_t sockaddr4len = sizeof(sockaddr4); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max4ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket4FD, + buf, + bufSize, + 0, + (struct sockaddr *)&sockaddr4, + &sockaddr4len); + LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket4FDBytesAvailable) + socket4FDBytesAvailable = 0; + else + socket4FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; + } + else + { + LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); + socket4FDBytesAvailable = 0; + free(buf); + } + } + else + { + NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); + LogVerbose(@"Receiving on IPv6"); + + struct sockaddr_in6 sockaddr6; + socklen_t sockaddr6len = sizeof(sockaddr6); + + // #222: GCD does not necessarily return the size of an entire UDP packet + // from dispatch_source_get_data(), so we must use the maximum packet size. + size_t bufSize = max6ReceiveSize; + void *buf = malloc(bufSize); + + result = recvfrom(socket6FD, + buf, + bufSize, + 0, + (struct sockaddr *)&sockaddr6, + &sockaddr6len); + LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); + + if (result > 0) + { + if ((size_t)result >= socket6FDBytesAvailable) + socket6FDBytesAvailable = 0; + else + socket6FDBytesAvailable -= result; + + if ((size_t)result != bufSize) { + buf = realloc(buf, result); + } + + data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; + addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; + } + else + { + LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); + socket6FDBytesAvailable = 0; + free(buf); + } + } + + + BOOL waitingForSocket = NO; + BOOL notifiedDelegate = NO; + BOOL ignored = NO; + + NSError *socketError = nil; + + if (result == 0) + { + waitingForSocket = YES; + } + else if (result < 0) + { + if (errno == EAGAIN) + waitingForSocket = YES; + else + socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; + } + else + { + if (flags & kDidConnect) + { + if (addr4 && ![self isConnectedToAddress4:addr4]) + ignored = YES; + if (addr6 && ![self isConnectedToAddress6:addr6]) + ignored = YES; + } + + NSData *addr = (addr4 != nil) ? addr4 : addr6; + + if (!ignored) + { + if (receiveFilterBlock && receiveFilterQueue) + { + // Run data through filter, and if approved, notify delegate + + __block id filterContext = nil; + __block BOOL allowed = NO; + + if (receiveFilterAsync) + { + pendingFilterOperations++; + dispatch_async(receiveFilterQueue, + ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + + // Transition back to socketQueue to get the current delegate / delegateQueue + dispatch_async(self->socketQueue, + ^{ @autoreleasepool { + + self->pendingFilterOperations--; + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + } + + if (self->flags & kReceiveOnce) + { + if (allowed) + { + // The delegate has been notified, + // so our receive once operation has completed. + self->flags &= ~kReceiveOnce; + } + else if (self->pendingFilterOperations == 0) + { + // All pending filter operations have completed, + // and none were allowed through. + // Our receive once operation hasn't completed yet. + [self doReceive]; + } + } + }}); + }}); + } + else // if (!receiveFilterAsync) + { + dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { + + allowed = self->receiveFilterBlock(data, addr, &filterContext); + }}); + + if (allowed) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; + notifiedDelegate = YES; + } + else + { + LogVerbose(@"received packet silently dropped by receiveFilter"); + ignored = YES; + } + } + } + else // if (!receiveFilterBlock || !receiveFilterQueue) + { + [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; + notifiedDelegate = YES; + } + } + } + + if (waitingForSocket) + { + // Wait for a notification of available data. + + if (socket4FDBytesAvailable == 0) { + [self resumeReceive4Source]; + } + if (socket6FDBytesAvailable == 0) { + [self resumeReceive6Source]; + } + } + else if (socketError) + { + [self closeWithError:socketError]; + } + else + { + if (flags & kReceiveContinuous) + { + // Continuous receive mode + [self doReceive]; + } + else + { + // One-at-a-time receive mode + if (notifiedDelegate) + { + // The delegate has been notified (no set filter). + // So our receive once operation has completed. + flags &= ~kReceiveOnce; + } + else if (ignored) + { + [self doReceive]; + } + else + { + // Waiting on asynchronous receive filter... + } + } + } } - (void)doReceiveEOF { - LogTrace(); + LogTrace(); - [self closeWithError:[self socketClosedError]]; + [self closeWithError:[self socketClosedError]]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4785,66 +4938,67 @@ - (void)doReceiveEOF - (void)closeWithError:(NSError *)error { - LogVerbose(@"closeWithError: %@", error); + LogVerbose(@"closeWithError: %@", error); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (currentSend) [self endCurrentSend]; + if (currentSend) [self endCurrentSend]; - [sendQueue removeAllObjects]; + [sendQueue removeAllObjects]; - // If a socket has been created, we should notify the delegate. - BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; + // If a socket has been created, we should notify the delegate. + BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; - // Close all sockets, send/receive sources, cfstreams, etc + // Close all sockets, send/receive sources, cfstreams, etc #if TARGET_OS_IPHONE - [self removeStreamsFromRunLoop]; - [self closeReadAndWriteStreams]; + [self removeStreamsFromRunLoop]; + [self closeReadAndWriteStreams]; #endif - [self closeSockets]; + [self closeSockets]; - // Clear all flags (config remains as is) - flags = 0; + // Clear all flags (config remains as is) + flags = 0; - if (shouldCallDelegate) - { - [self notifyDidCloseWithError:error]; - } + if (shouldCallDelegate) + { + [self notifyDidCloseWithError:error]; + } } - (void)close { - LogTrace(); + LogTrace(); - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - [self closeWithError:nil]; - }}; + [self closeWithError:nil]; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); } - (void)closeAfterSending { - LogTrace(); + LogTrace(); - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - self->flags |= kCloseAfterSends; + self->flags |= kCloseAfterSends; - if (self->currentSend == nil && [self->sendQueue count] == 0) - { - [self closeWithError:nil]; - } - }}; + if (self->currentSend == nil && [self->sendQueue count] == 0) + { + [self closeWithError:nil]; + } + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -4860,459 +5014,517 @@ + (void)ignore:(id)_ + (void)startListenerThreadIfNeeded { - static dispatch_once_t predicate; - dispatch_once(&predicate, ^{ + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ - listenerThread = [[NSThread alloc] initWithTarget:self - selector:@selector(listenerThread:) - object:nil]; - [listenerThread start]; - }); + listenerThread = [[NSThread alloc] initWithTarget:self + selector:@selector(listenerThread:) + object:nil]; + [listenerThread start]; + }); } + (void)listenerThread:(id)unused { - @autoreleasepool { + @autoreleasepool { - [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; + [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; - LogInfo(@"ListenerThread: Started"); + LogInfo(@"ListenerThread: Started"); - // We can't run the run loop unless it has an associated input source or a timer. - // So we'll just create a timer that will never fire - unless the server runs for a decades. - [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] - target:self - selector:@selector(ignore:) - userInfo:nil - repeats:YES]; + // We can't run the run loop unless it has an associated input source or a timer. + // So we'll just create a timer that will never fire - unless the server runs for a decades. + [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] + target:self + selector:@selector(ignore:) + userInfo:nil + repeats:YES]; - [[NSRunLoop currentRunLoop] run]; + [[NSRunLoop currentRunLoop] run]; - LogInfo(@"ListenerThread: Stopped"); - } + LogInfo(@"ListenerThread: Stopped"); + } } + (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket { - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, + @"Invoked on wrong thread"); - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - if (asyncUdpSocket->readStream4) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->readStream4) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->readStream6) - CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->readStream6) + CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->writeStream4) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->writeStream4) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->writeStream6) - CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->writeStream6) + CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, + runLoop, + kCFRunLoopDefaultMode); } + (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket { - LogTrace(); - NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); + LogTrace(); + NSAssert([NSThread currentThread] == listenerThread, + @"Invoked on wrong thread"); - CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); - if (asyncUdpSocket->readStream4) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->readStream4) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->readStream6) - CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->readStream6) + CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->writeStream4) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->writeStream4) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, + runLoop, + kCFRunLoopDefaultMode); - if (asyncUdpSocket->writeStream6) - CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); + if (asyncUdpSocket->writeStream6) + CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, + runLoop, + kCFRunLoopDefaultMode); } -static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo) +static void CFReadStreamCallback(CFReadStreamRef stream, + CFStreamEventType type, + void *pInfo) { - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFReadStreamCallback - Open"); - break; - } - case kCFStreamEventHasBytesAvailable: - { - LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFReadStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->readStream4 && - stream != asyncUdpSocket->readStream6 ) - { - LogCVerbose(@"CFReadStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); - } - } -#pragma clang diagnostic pop - } -} + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFReadStreamCallback - Open"); + break; + } + case kCFStreamEventHasBytesAvailable: + { + LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } -static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) -{ - @autoreleasepool { - GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; + dispatch_async(asyncUdpSocket->socketQueue, + ^{ @autoreleasepool { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wswitch-enum" - switch(type) - { - case kCFStreamEventOpenCompleted: - { - LogCVerbose(@"CFWriteStreamCallback - Open"); - break; - } - case kCFStreamEventCanAcceptBytes: - { - LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); - break; - } - case kCFStreamEventErrorOccurred: - case kCFStreamEventEndEncountered: - { - NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); - if (error == nil && type == kCFStreamEventEndEncountered) - { - error = [asyncUdpSocket socketClosedError]; - } - - dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { - - LogCVerbose(@"CFWriteStreamCallback - %@", - (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - - if (stream != asyncUdpSocket->writeStream4 && - stream != asyncUdpSocket->writeStream6 ) - { - LogCVerbose(@"CFWriteStreamCallback - Ignored"); - return_from_block; - } - - [asyncUdpSocket closeWithError:error]; - - }}); - - break; - } - default: - { - LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); - } - } -#pragma clang diagnostic pop - } -} + LogCVerbose(@"CFReadStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); -- (BOOL)createReadAndWriteStreams:(NSError **)errPtr -{ - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + if (stream != asyncUdpSocket->readStream4 && + stream != asyncUdpSocket->readStream6 ) + { + LogCVerbose(@"CFReadStreamCallback - Ignored"); + return_from_block; + } + + [asyncUdpSocket closeWithError:error]; - NSError *err = nil; + }}); - if (readStream4 || writeStream4 || readStream6 || writeStream6) - { - // Streams already created - return YES; - } + break; + } + default: + { + LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); + } + } +#pragma clang diagnostic pop + } +} - if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) - { - err = [self otherError:@"Cannot create streams without a file descriptor"]; - goto Failed; - } +static void CFWriteStreamCallback(CFWriteStreamRef stream, + CFStreamEventType type, + void *pInfo) +{ + @autoreleasepool { + GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; - // Create streams +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-enum" + switch(type) + { + case kCFStreamEventOpenCompleted: + { + LogCVerbose(@"CFWriteStreamCallback - Open"); + break; + } + case kCFStreamEventCanAcceptBytes: + { + LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); + break; + } + case kCFStreamEventErrorOccurred: + case kCFStreamEventEndEncountered: + { + NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); + if (error == nil && type == kCFStreamEventEndEncountered) + { + error = [asyncUdpSocket socketClosedError]; + } - LogVerbose(@"Creating read and write stream(s)..."); + dispatch_async(asyncUdpSocket->socketQueue, + ^{ @autoreleasepool { - if (socket4FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4); - if (!readStream4 || !writeStream4) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; - goto Failed; - } - } + LogCVerbose(@"CFWriteStreamCallback - %@", + (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); - if (socket6FD != SOCKET_NULL) - { - CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6); - if (!readStream6 || !writeStream6) - { - err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; - goto Failed; - } - } + if (stream != asyncUdpSocket->writeStream4 && + stream != asyncUdpSocket->writeStream6 ) + { + LogCVerbose(@"CFWriteStreamCallback - Ignored"); + return_from_block; + } - // Ensure the CFStream's don't close our underlying socket + [asyncUdpSocket closeWithError:error]; - CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + }}); - CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); - CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + break; + } + default: + { + LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); + } + } +#pragma clang diagnostic pop + } +} - return YES; +- (BOOL)createReadAndWriteStreams:(NSError **)errPtr +{ + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + + NSError *err = nil; + + if (readStream4 || writeStream4 || readStream6 || writeStream6) + { + // Streams already created + return YES; + } + + if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) + { + err = [self otherError:@"Cannot create streams without a file descriptor"]; + goto Failed; + } + + // Create streams + + LogVerbose(@"Creating read and write stream(s)..."); + + if (socket4FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, + (CFSocketNativeHandle)socket4FD, + &readStream4, + &writeStream4); + if (!readStream4 || !writeStream4) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + CFStreamCreatePairWithSocket(NULL, + (CFSocketNativeHandle)socket6FD, + &readStream6, + &writeStream6); + if (!readStream6 || !writeStream6) + { + err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; + goto Failed; + } + } + + // Ensure the CFStream's don't close our underlying socket + + CFReadStreamSetProperty(readStream4, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream4, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); + + CFReadStreamSetProperty(readStream6, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); + CFWriteStreamSetProperty(writeStream6, + kCFStreamPropertyShouldCloseNativeSocket, + kCFBooleanFalse); + + return YES; Failed: - if (readStream4) - { - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } - - if (errPtr) - *errPtr = err; - - return NO; + if (readStream4) + { + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } + + if (errPtr) + *errPtr = err; + + return NO; } - (BOOL)registerForStreamCallbacks:(NSError **)errPtr { - LogTrace(); - - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); - - NSError *err = nil; - - streamContext.version = 0; - streamContext.info = (__bridge void *)self; - streamContext.retain = nil; - streamContext.release = nil; - streamContext.copyDescription = nil; - - CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; - -// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); -// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); - - if (socket4FD != SOCKET_NULL) - { - if (readStream4 == NULL || writeStream4 == NULL) - { - err = [self otherError:@"Read/Write stream4 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; - goto Failed; - } - } - - if (socket6FD != SOCKET_NULL) - { - if (readStream6 == NULL || writeStream6 == NULL) - { - err = [self otherError:@"Read/Write stream6 is null"]; - goto Failed; - } - - BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext); - BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext); - - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; - goto Failed; - } - } - - return YES; + LogTrace(); + + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, + @"Read/Write streams are null"); + + NSError *err = nil; + + streamContext.version = 0; + streamContext.info = (__bridge void *)self; + streamContext.retain = nil; + streamContext.release = nil; + streamContext.copyDescription = nil; + + CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; + + // readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); + // writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); + + if (socket4FD != SOCKET_NULL) + { + if (readStream4 == NULL || writeStream4 == NULL) + { + err = [self otherError:@"Read/Write stream4 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream4, + readStreamEvents, + &CFReadStreamCallback, + &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream4, + writeStreamEvents, + &CFWriteStreamCallback, + &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; + goto Failed; + } + } + + if (socket6FD != SOCKET_NULL) + { + if (readStream6 == NULL || writeStream6 == NULL) + { + err = [self otherError:@"Read/Write stream6 is null"]; + goto Failed; + } + + BOOL r1 = CFReadStreamSetClient(readStream6, + readStreamEvents, + &CFReadStreamCallback, + &streamContext); + BOOL r2 = CFWriteStreamSetClient(writeStream6, + writeStreamEvents, + &CFWriteStreamCallback, + &streamContext); + + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; + goto Failed; + } + } + + return YES; Failed: - if (readStream4) { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - } - if (writeStream4) { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - } - if (readStream6) { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - } - if (writeStream6) { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - } - - if (errPtr) *errPtr = err; - return NO; + if (readStream4) { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + } + if (writeStream4) { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + } + if (readStream6) { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + } + if (writeStream6) { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + } + + if (errPtr) *errPtr = err; + return NO; } - (BOOL)addStreamsToRunLoop:(NSError **)errPtr { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, + @"Read/Write streams are null"); - if (!(flags & kAddedStreamListener)) - { - [[self class] startListenerThreadIfNeeded]; - [[self class] performSelector:@selector(addStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; + if (!(flags & kAddedStreamListener)) + { + [[self class] startListenerThreadIfNeeded]; + [[self class] performSelector:@selector(addStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; - flags |= kAddedStreamListener; - } + flags |= kAddedStreamListener; + } - return YES; + return YES; } - (BOOL)openStreams:(NSError **)errPtr { - LogTrace(); + LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); - NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); + NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, + @"Read/Write streams are null"); - NSError *err = nil; + NSError *err = nil; - if (socket4FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream4); - BOOL r2 = CFWriteStreamOpen(writeStream4); + if (socket4FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream4); + BOOL r2 = CFWriteStreamOpen(writeStream4); - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; - goto Failed; - } - } + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; + goto Failed; + } + } - if (socket6FD != SOCKET_NULL) - { - BOOL r1 = CFReadStreamOpen(readStream6); - BOOL r2 = CFWriteStreamOpen(writeStream6); + if (socket6FD != SOCKET_NULL) + { + BOOL r1 = CFReadStreamOpen(readStream6); + BOOL r2 = CFWriteStreamOpen(writeStream6); - if (!r1 || !r2) - { - err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; - goto Failed; - } - } + if (!r1 || !r2) + { + err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; + goto Failed; + } + } - return YES; + return YES; Failed: - if (errPtr) *errPtr = err; - return NO; + if (errPtr) *errPtr = err; + return NO; } - (void)removeStreamsFromRunLoop { - LogTrace(); - NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); + LogTrace(); + NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), + @"Must be dispatched on socketQueue"); - if (flags & kAddedStreamListener) - { - [[self class] performSelector:@selector(removeStreamListener:) - onThread:listenerThread - withObject:self - waitUntilDone:YES]; + if (flags & kAddedStreamListener) + { + [[self class] performSelector:@selector(removeStreamListener:) + onThread:listenerThread + withObject:self + waitUntilDone:YES]; - flags &= ~kAddedStreamListener; - } + flags &= ~kAddedStreamListener; + } } - (void)closeReadAndWriteStreams { - LogTrace(); - - if (readStream4) - { - CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream4); - CFRelease(readStream4); - readStream4 = NULL; - } - if (writeStream4) - { - CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream4); - CFRelease(writeStream4); - writeStream4 = NULL; - } - if (readStream6) - { - CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); - CFReadStreamClose(readStream6); - CFRelease(readStream6); - readStream6 = NULL; - } - if (writeStream6) - { - CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); - CFWriteStreamClose(writeStream6); - CFRelease(writeStream6); - writeStream6 = NULL; - } + LogTrace(); + + if (readStream4) + { + CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream4); + CFRelease(readStream4); + readStream4 = NULL; + } + if (writeStream4) + { + CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream4); + CFRelease(writeStream4); + writeStream4 = NULL; + } + if (readStream6) + { + CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(readStream6); + CFRelease(readStream6); + readStream6 = NULL; + } + if (writeStream6) + { + CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); + CFWriteStreamClose(writeStream6); + CFRelease(writeStream6); + writeStream6 = NULL; + } } #endif @@ -5320,21 +5532,21 @@ - (void)closeReadAndWriteStreams #if TARGET_OS_IPHONE - (void)applicationWillEnterForeground:(NSNotification *)notification { - LogTrace(); + LogTrace(); - // If the application was backgrounded, then iOS may have shut down our sockets. - // So we take a quick look to see if any of them received an EOF. + // If the application was backgrounded, then iOS may have shut down our sockets. + // So we take a quick look to see if any of them received an EOF. - dispatch_block_t block = ^{ @autoreleasepool { + dispatch_block_t block = ^{ @autoreleasepool { - [self resumeReceive4Source]; - [self resumeReceive6Source]; - }}; + [self resumeReceive4Source]; + [self resumeReceive6Source]; + }}; - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_async(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_async(socketQueue, block); } #endif @@ -5347,8 +5559,11 @@ - (void)applicationWillEnterForeground:(NSNotification *)notification **/ - (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue { - void *nonNullUnusedPointer = (__bridge void *)self; - dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); + void *nonNullUnusedPointer = (__bridge void *)self; + dispatch_queue_set_specific(socketNewTargetQueue, + IsOnSocketQueueOrTargetQueueKey, + nonNullUnusedPointer, + NULL); } /** @@ -5356,161 +5571,164 @@ - (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue **/ - (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue { - dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); + dispatch_queue_set_specific(socketOldTargetQueue, + IsOnSocketQueueOrTargetQueueKey, + NULL, + NULL); } - (void)performBlock:(dispatch_block_t)block { - if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - block(); - else - dispatch_sync(socketQueue, block); + if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + block(); + else + dispatch_sync(socketQueue, block); } - (int)socketFD { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } - if (socket4FD != SOCKET_NULL) - return socket4FD; - else - return socket6FD; + if (socket4FD != SOCKET_NULL) + return socket4FD; + else + return socket6FD; } - (int)socket4FD { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } - return socket4FD; + return socket4FD; } - (int)socket6FD { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return SOCKET_NULL; - } + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return SOCKET_NULL; + } - return socket6FD; + return socket6FD; } #if TARGET_OS_IPHONE - (CFReadStreamRef)readStream { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } - // Todo... + // Todo... - if (readStream4) - return readStream4; - else - return readStream6; + if (readStream4) + return readStream4; + else + return readStream6; } - (CFWriteStreamRef)writeStream { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NULL; - } + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NULL; + } - NSError *err = nil; - if (![self createReadAndWriteStreams:&err]) - { - LogError(@"Error creating CFStream(s): %@", err); - return NULL; - } + NSError *err = nil; + if (![self createReadAndWriteStreams:&err]) + { + LogError(@"Error creating CFStream(s): %@", err); + return NULL; + } - if (writeStream4) - return writeStream4; - else - return writeStream6; + if (writeStream4) + return writeStream4; + else + return writeStream6; } - (BOOL)enableBackgroundingOnSockets { - if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) - { - LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", - THIS_FILE, THIS_METHOD); - return NO; - } - - // Why is this commented out? - // See comments below. - -// NSError *err = nil; -// if (![self createReadAndWriteStreams:&err]) -// { -// LogError(@"Error creating CFStream(s): %@", err); -// return NO; -// } -// -// LogVerbose(@"Enabling backgrouding on socket"); -// -// BOOL r1, r2; -// -// if (readStream4 && writeStream4) -// { -// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// -// if (!r1 || !r2) -// { -// LogError(@"Error setting voip type (IPv4)"); -// return NO; -// } -// } -// -// if (readStream6 && writeStream6) -// { -// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -// -// if (!r1 || !r2) -// { -// LogError(@"Error setting voip type (IPv6)"); -// return NO; -// } -// } -// -// return YES; - - // The above code will actually appear to work. - // The methods will return YES, and everything will appear fine. - // - // One tiny problem: the sockets will still get closed when the app gets backgrounded. - // - // Apple does not officially support backgrounding UDP sockets. - - return NO; + if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) + { + LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", + THIS_FILE, THIS_METHOD); + return NO; + } + + // Why is this commented out? + // See comments below. + + // NSError *err = nil; + // if (![self createReadAndWriteStreams:&err]) + // { + // LogError(@"Error creating CFStream(s): %@", err); + // return NO; + // } + // + // LogVerbose(@"Enabling backgrouding on socket"); + // + // BOOL r1, r2; + // + // if (readStream4 && writeStream4) + // { + // r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + // r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + // + // if (!r1 || !r2) + // { + // LogError(@"Error setting voip type (IPv4)"); + // return NO; + // } + // } + // + // if (readStream6 && writeStream6) + // { + // r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + // r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + // + // if (!r1 || !r2) + // { + // LogError(@"Error setting voip type (IPv6)"); + // return NO; + // } + // } + // + // return YES; + + // The above code will actually appear to work. + // The methods will return YES, and everything will appear fine. + // + // One tiny problem: the sockets will still get closed when the app gets backgrounded. + // + // Apple does not officially support backgrounding UDP sockets. + + return NO; } #endif @@ -5521,122 +5739,128 @@ - (BOOL)enableBackgroundingOnSockets + (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 { - char addrBuf[INET_ADDRSTRLEN]; + char addrBuf[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } + if (inet_ntop(AF_INET, + &pSockaddr4->sin_addr, + addrBuf, + (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; } + (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 { - char addrBuf[INET6_ADDRSTRLEN]; + char addrBuf[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) - { - addrBuf[0] = '\0'; - } + if (inet_ntop(AF_INET6, + &pSockaddr6->sin6_addr, + addrBuf, + (socklen_t)sizeof(addrBuf)) == NULL) + { + addrBuf[0] = '\0'; + } - return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; + return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; } + (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 { - return ntohs(pSockaddr4->sin_port); + return ntohs(pSockaddr4->sin_port); } + (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 { - return ntohs(pSockaddr6->sin6_port); + return ntohs(pSockaddr6->sin6_port); } + (NSString *)hostFromAddress:(NSData *)address { - NSString *host = nil; - [self getHost:&host port:NULL family:NULL fromAddress:address]; + NSString *host = nil; + [self getHost:&host port:NULL family:NULL fromAddress:address]; - return host; + return host; } + (uint16_t)portFromAddress:(NSData *)address { - uint16_t port = 0; - [self getHost:NULL port:&port family:NULL fromAddress:address]; + uint16_t port = 0; + [self getHost:NULL port:&port family:NULL fromAddress:address]; - return port; + return port; } + (int)familyFromAddress:(NSData *)address { - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; - return af; + return af; } + (BOOL)isIPv4Address:(NSData *)address { - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; - return (af == AF_INET); + return (af == AF_INET); } + (BOOL)isIPv6Address:(NSData *)address { - int af = AF_UNSPEC; - [self getHost:NULL port:NULL family:&af fromAddress:address]; + int af = AF_UNSPEC; + [self getHost:NULL port:NULL family:&af fromAddress:address]; - return (af == AF_INET6); + return (af == AF_INET6); } + (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address { - return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; + return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; } + (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address { - if ([address length] >= sizeof(struct sockaddr)) - { - const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; - - if (addrX->sa_family == AF_INET) - { - if ([address length] >= sizeof(struct sockaddr_in)) - { - const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; - if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; - if (afPtr) *afPtr = AF_INET; - - return YES; - } - } - else if (addrX->sa_family == AF_INET6) - { - if ([address length] >= sizeof(struct sockaddr_in6)) - { - const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; - - if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; - if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; - if (afPtr) *afPtr = AF_INET6; - - return YES; - } - } - } - - if (hostPtr) *hostPtr = nil; - if (portPtr) *portPtr = 0; - if (afPtr) *afPtr = AF_UNSPEC; - - return NO; + if ([address length] >= sizeof(struct sockaddr)) + { + const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; + + if (addrX->sa_family == AF_INET) + { + if ([address length] >= sizeof(struct sockaddr_in)) + { + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; + if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; + if (afPtr) *afPtr = AF_INET; + + return YES; + } + } + else if (addrX->sa_family == AF_INET6) + { + if ([address length] >= sizeof(struct sockaddr_in6)) + { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX; + + if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; + if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; + if (afPtr) *afPtr = AF_INET6; + + return YES; + } + } + } + + if (hostPtr) *hostPtr = nil; + if (portPtr) *portPtr = 0; + if (afPtr) *afPtr = AF_UNSPEC; + + return NO; } @end