//
//  GCDAsyncSocket.m
//
//  This class is in the public domain.
//  Originally created by Robbie Hanson in Q4 2010.
//  Updated and maintained by Deusty LLC and the Apple development community.
//
//  https://github.com/robbiehanson/CocoaAsyncSocket
//

#import "GCDAsyncSocket.h"

#if TARGET_OS_IPHONE
#import <Network/Network.h>
#import <CoreFoundation/CFStream.h>
// Note: CFStream APIs are still used for TLS support and are part of CoreFoundation
// CFStream SSL constants are needed only for the legacy CFStream TLS path (when GCDAsyncSocketUseCFStreamForTLS is set)
// The default path uses SecureTransport which doesn't require these constants
// Declare SSL constants as extern to avoid importing deprecated CFNetwork framework
// These symbols are linked at runtime from CFNetwork framework
extern const CFStringRef kCFStreamPropertySSLSettings;
extern const CFStringRef kCFStreamSSLPeerName;
extern const CFStringRef kCFStreamSSLCertificates;
extern const CFStringRef kCFStreamSSLIsServer;
#endif

#import <TargetConditionals.h>
#import <arpa/inet.h>
#import <fcntl.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <netinet/in.h>
#import <net/if.h>
#import <sys/socket.h>
#import <sys/types.h>
#import <sys/ioctl.h>
#import <sys/poll.h>
#import <sys/uio.h>
#import <sys/un.h>
#import <unistd.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"

#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
#endif


#ifndef GCDAsyncSocketLoggingEnabled
#define GCDAsyncSocketLoggingEnabled 0
#endif

#if GCDAsyncSocketLoggingEnabled

// Logging Enabled - See log level below

// Logging uses the CocoaLumberjack framework (which is also GCD based).
// https://github.com/robbiehanson/CocoaLumberjack
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"

#define LogAsync   YES
#define LogContext GCDAsyncSocketLoggingContext

#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...)    LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)

#define LogError(frmt, ...)     LogObjc(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...)      LogObjc(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...)      LogObjc(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...)   LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)

#define LogCError(frmt, ...)    LogC(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...)     LogC(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...)     LogC(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...)  LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)

#define LogTrace()              LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace()             LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)

#ifndef GCDAsyncSocketLogLevel
#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
#endif

// Log levels : off, error, warn, info, verbose
static const int logLevel = GCDAsyncSocketLogLevel;

#else

// Logging Disabled

#define LogError(frmt, ...)     do {} while (0)
#define LogWarn(frmt, ...)      do {} while (0)
#define LogInfo(frmt, ...)      do {} while (0)
#define LogVerbose(frmt, ...)   do {} while (0)

#define LogCError(frmt, ...)    do {} while (0)
#define LogCWarn(frmt, ...)     do {} while (0)
#define LogCInfo(frmt, ...)     do {} while (0)
#define LogCVerbose(frmt, ...)  do {} while (0)

#define LogTrace()              do {} while (0)
#define LogCTrace(frmt, ...)    do {} while (0)

#endif

/**
 * 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


NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";

NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";

NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust";
#if TARGET_OS_IPHONE
NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS";
#endif
NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID";
NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart";
NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord";
NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
NSString *const GCDAsyncSocketSSLALPN = @"GCDAsyncSocketSSLALPN";
#if !TARGET_OS_IPHONE
NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
#endif

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
#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
#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
};

#if TARGET_OS_IPHONE
static NSThread *cfstreamThread;  // Used for CFStreams


static uint64_t cfstreamThreadRetainCount;   // setup & teardown
static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * A PreBuffer is used when there is more data available on the socket
 * than is being requested by current read request.
 * In this case we slurp up all data from the socket (to minimize sys calls),
 * and store additional yet unread data in a "prebuffer".
 *
 * The prebuffer is entirely drained before we read from the socket again.
 * In other words, a large chunk of data is written is written to the prebuffer.
 * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
 *
 * A ring buffer was once used for this purpose.
 * But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
 * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
 * 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 *readPointer;
  uint8_t *writePointer;
}

- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER;

- (void)ensureCapacityForWrite:(size_t)numBytes;

- (size_t)availableBytes;
- (uint8_t *)readBuffer;

- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr;

- (size_t)availableSpace;
- (uint8_t *)writeBuffer;

- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr;

- (void)didRead:(size_t)bytesRead;
- (void)didWrite:(size_t)bytesWritten;

- (void)reset;

@end

@implementation GCDAsyncSocketPreBuffer

// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
  NSAssert(0, @"Use the designated initializer");
  return nil;
}

- (instancetype)initWithCapacity:(size_t)numBytes
{
  if ((self = [super init]))
  {
    preBufferSize = numBytes;
    preBuffer = (uint8_t *)malloc(preBufferSize);

    readPointer = preBuffer;
    writePointer = preBuffer;
  }
  return self;
}

- (void)dealloc
{
  if (preBuffer)
    free(preBuffer);
}

- (void)ensureCapacityForWrite:(size_t)numBytes
{
  size_t availableSpace = [self availableSpace];

  if (numBytes > availableSpace)
  {
    size_t additionalBytes = numBytes - availableSpace;

    size_t newPreBufferSize = preBufferSize + additionalBytes;
    uint8_t *newPreBuffer = (uint8_t *)realloc(preBuffer, newPreBufferSize);

    size_t readPointerOffset = readPointer - preBuffer;
    size_t writePointerOffset = writePointer - preBuffer;

    preBuffer = newPreBuffer;
    preBufferSize = newPreBufferSize;

    readPointer = preBuffer + readPointerOffset;
    writePointer = preBuffer + writePointerOffset;
  }
}

- (size_t)availableBytes
{
  return writePointer - readPointer;
}

- (uint8_t *)readBuffer
{
  return readPointer;
}

- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
{
  if (bufferPtr) *bufferPtr = readPointer;
  if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
}

- (void)didRead:(size_t)bytesRead
{
  readPointer += bytesRead;

  if (readPointer == writePointer)
  {
    // The prebuffer has been drained. Reset pointers.
    readPointer  = preBuffer;
    writePointer = preBuffer;
  }
}

- (size_t)availableSpace
{
  return preBufferSize - (writePointer - preBuffer);
}

- (uint8_t *)writeBuffer
{
  return writePointer;
}

- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
{
  if (bufferPtr) *bufferPtr = writePointer;
  if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
}

- (void)didWrite:(size_t)bytesWritten
{
  writePointer += bytesWritten;
}

- (void)reset
{
  readPointer  = preBuffer;
  writePointer = preBuffer;
}

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * The GCDAsyncReadPacket encompasses the instructions for any given read.
 * The content of a read packet allows the code to determine if we're:
 *  - reading to a certain length
 *  - reading to a certain separator
 *  - or simply reading the first chunk of 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;
}
- (instancetype)initWithData:(NSMutableData *)d
                 startOffset:(NSUInteger)s
                   maxLength:(NSUInteger)m
                     timeout:(NSTimeInterval)t
                  readLength:(NSUInteger)l
                  terminator:(NSData *)e
                         tag:(long)i NS_DESIGNATED_INITIALIZER;

- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;

- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;

- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;

- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;

@end

@implementation GCDAsyncReadPacket

// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
  NSAssert(0, @"Use the designated initializer");
  return nil;
}

- (instancetype)initWithData:(NSMutableData *)d
                 startOffset:(NSUInteger)s
                   maxLength:(NSUInteger)m
                     timeout:(NSTimeInterval)t
                  readLength:(NSUInteger)l
                  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;
}

/**
 * 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 buffSpace = buffSize - buffUsed;

  if (bytesToRead > buffSpace)
  {
    NSUInteger buffInc = bytesToRead - buffSpace;

    [buffer increaseLengthBy:buffInc];
  }
}

/**
 * This method is used when we do NOT know how much data is available to be read from the socket.
 * This method returns the default value unless it exceeds the specified readLength or maxLength.
 *
 * 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;
}

/**
 * For read packets without a set terminator, returns the amount of data
 * that can be read without exceeding the readLength or maxLength.
 *
 * The given parameter indicates the number of bytes estimated to be available on the socket,
 * 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");

  if (readLength > 0)
  {
    // Read a specific length of data

    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

    NSUInteger result = bytesAvailable;

    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.

    return result;
  }
}

/**
 * For read packets with a set terminator, returns the amount of data
 * that can be read without exceeding the maxLength.
 *
 * The given parameter indicates the number of bytes estimated to be available on the socket,
 * which is taken into consideration during the calculation.
 *
 * 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;
}

/**
 * For read packets with a set terminator,
 * returns the amount of data that can be read from the given preBuffer,
 * 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;
  }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvla"
  uint8_t seq[termLength];
#pragma clang diagnostic pop
  const void *termBuf = [term bytes];

  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 loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.

  NSUInteger result = maxPreBufferLength;

  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);

      if (memcmp(seq, termBuf, termLength) == 0)
      {
        result = preLen;
        found = YES;
        break;
      }

      buf++;
      bufLen--;
      preLen++;
    }
    else
    {
      // Comparing directly from preBuffer

      if (memcmp(pre, termBuf, termLength) == 0)
      {
        NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic

        result = preOffset + termLength;
        found = YES;
        break;
      }

      pre++;
    }
  }

  // There is no need to avoid resizing the buffer in this particular situation.

  if (foundPtr) *foundPtr = found;
  return result;
}

/**
 * For read packets with a set terminator, scans the packet buffer for the term.
 * It is assumed the terminator had not been fully read prior to the new bytes.
 *
 * If the term is found, the number of excess bytes after the term are returned.
 * If the term is not found, this method will return -1.
 *
 * Note: A return value of zero means the term was found at the very end.
 *
 * 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");

  // 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 = (uint8_t *)[buffer mutableBytes];
  NSUInteger buffLength = bytesDone + numBytes;

  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.

  NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;

  while (i + termLength <= buffLength)
  {
    uint8_t *subBuffer = buff + startOffset + i;

    if (memcmp(subBuffer, termBuff, termLength) == 0)
    {
      return buffLength - (i + termLength);
    }

    i++;
  }

  return -1;
}


@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * The GCDAsyncWritePacket encompasses the instructions for any given write.
 **/
@interface GCDAsyncWritePacket : NSObject
{
@public
  NSData *buffer;
  NSUInteger bytesDone;
  long tag;
  NSTimeInterval timeout;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
@end

@implementation GCDAsyncWritePacket

// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
  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;
}


@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * 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;
}
- (instancetype)initWithTLSSettings:(NSDictionary <NSString*,
                                     NSObject*>*)settings NS_DESIGNATED_INITIALIZER;
@end

@implementation GCDAsyncSpecialPacket

// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
  NSAssert(0, @"Use the designated initializer");
  return nil;
}

- (instancetype)initWithTLSSettings:(NSDictionary <NSString*,
                                     NSObject*>*)settings
{
  if((self = [super init]))
  {
    tlsSettings = [settings copy];
  }
  return self;
}


@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@implementation GCDAsyncSocket
{
  uint32_t flags;
  uint16_t config;

  __weak id<GCDAsyncSocketDelegate> delegate;
  dispatch_queue_t delegateQueue;

  int socket4FD;
  int socket6FD;
  int socketUN;
  NSURL *socketUrl;
  int stateIndex;
  NSData * connectInterface4;
  NSData * connectInterface6;
  NSData * connectInterfaceUN;

  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;

  NSMutableArray *readQueue;
  NSMutableArray *writeQueue;

  GCDAsyncReadPacket *currentRead;
  GCDAsyncWritePacket *currentWrite;

  unsigned long socketFDBytesAvailable;

  GCDAsyncSocketPreBuffer *preBuffer;

#if TARGET_OS_IPHONE
  CFStreamClientContext streamContext;
  CFReadStreamRef readStream;
  CFWriteStreamRef writeStream;
#endif
  SSLContextRef sslContext;
  GCDAsyncSocketPreBuffer *sslPreBuffer;
  size_t sslWriteCachedLength;
  OSStatus sslErrCode;
  OSStatus lastSSLHandshakeError;

  void *IsOnSocketQueueOrTargetQueueKey;

  id userData;
  NSTimeInterval alternateAddressDelay;
}

- (instancetype)init
{
  return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}

- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
{
  return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}

- (instancetype)initWithDelegate:(id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq
{
  return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}

- (instancetype)initWithDelegate:(id<GCDAsyncSocketDelegate>)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;
}

- (void)dealloc
{
  LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);

  // 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];
    });
  }

  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;

  LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
}

#pragma mark -

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error {
  return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error];
}

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error {
  return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error];
}

+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error
{
  GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq];

  __block NSError *innerError = nil;
  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);

      NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

      innerError = [NSError errorWithDomain:GCDAsyncSocketErrorDomain
                                       code:GCDAsyncSocketOtherError
                                   userInfo:userInfo];
      return;
    }

    if (addr.sa_family == AF_INET)
    {
      socket->socket4FD = socketFD;
    }
    else if (addr.sa_family == AF_INET6)
    {
      socket->socket6FD = socketFD;
    }
    else
    {
      NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
                                                           @"GCDAsyncSocket",
                                                           [NSBundle mainBundle],
                                                           @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6",
                                                           nil);

      NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

      innerError = [NSError errorWithDomain:GCDAsyncSocketErrorDomain
                                       code:GCDAsyncSocketOtherError
                                   userInfo:userInfo];
      return;
    }

    socket->flags = kSocketStarted;
    [socket didConnect:socket->stateIndex];
  }});

  if (nil != innerError && nil != error) {
    *error = innerError;
  }
  return nil == innerError ? socket : nil;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (id)delegate
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return delegate;
  }
  else
  {
    __block id result;

    dispatch_sync(socketQueue, ^{
      result = self->delegate;
    });

    return result;
  }
}

- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
{
  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);
  }
}

- (void)setDelegate:(id<GCDAsyncSocketDelegate>)newDelegate
{
  [self setDelegate:newDelegate synchronously:NO];
}

- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)newDelegate
{
  [self setDelegate:newDelegate synchronously:YES];
}

- (dispatch_queue_t)delegateQueue
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return delegateQueue;
  }
  else
  {
    __block dispatch_queue_t result;

    dispatch_sync(socketQueue, ^{
      result = self->delegateQueue;
    });

    return result;
  }
}

- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
  dispatch_block_t block = ^{

#if !OS_OBJECT_USE_OBJC
    if (self->delegateQueue) dispatch_release(self->delegateQueue);
    if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif

    self->delegateQueue = newDelegateQueue;
  };

  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];
}

- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
  [self setDelegateQueue:newDelegateQueue synchronously:YES];
}

- (void)getDelegate:(id<GCDAsyncSocketDelegate> *)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 (delegatePtr) *delegatePtr = dPtr;
    if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
  }
}

- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
  dispatch_block_t block = ^{

    self->delegate = newDelegate;

#if !OS_OBJECT_USE_OBJC
    if (self->delegateQueue) dispatch_release(self->delegateQueue);
    if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif

    self->delegateQueue = newDelegateQueue;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
    block();
  }
  else {
    if (synchronously)
      dispatch_sync(socketQueue, block);
    else
      dispatch_async(socketQueue, block);
  }
}

- (void)setDelegate:(id<GCDAsyncSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
  [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}

- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
  [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}

- (BOOL)isIPv4Enabled
{
  // Note: YES means kIPv4Disabled is OFF

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return ((config & kIPv4Disabled) == 0);
  }
  else
  {
    __block BOOL result;

    dispatch_sync(socketQueue, ^{
      result = ((self->config & kIPv4Disabled) == 0);
    });

    return result;
  }
}

- (void)setIPv4Enabled:(BOOL)flag
{
  // Note: YES means kIPv4Disabled is OFF

  dispatch_block_t block = ^{

    if (flag)
      self->config &= ~kIPv4Disabled;
    else
      self->config |= kIPv4Disabled;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_async(socketQueue, block);
}

- (BOOL)isIPv6Enabled
{
  // Note: YES means kIPv6Disabled is OFF

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return ((config & kIPv6Disabled) == 0);
  }
  else
  {
    __block BOOL result;

    dispatch_sync(socketQueue, ^{
      result = ((self->config & kIPv6Disabled) == 0);
    });

    return result;
  }
}

- (void)setIPv6Enabled:(BOOL)flag
{
  // Note: YES means kIPv6Disabled is OFF

  dispatch_block_t block = ^{

    if (flag)
      self->config &= ~kIPv6Disabled;
    else
      self->config |= kIPv6Disabled;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_async(socketQueue, block);
}

- (BOOL)isIPv4PreferredOverIPv6
{
  // Note: YES means kPreferIPv6 is OFF

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return ((config & kPreferIPv6) == 0);
  }
  else
  {
    __block BOOL result;

    dispatch_sync(socketQueue, ^{
      result = ((self->config & kPreferIPv6) == 0);
    });

    return result;
  }
}

- (void)setIPv4PreferredOverIPv6:(BOOL)flag
{
  // Note: YES means kPreferIPv6 is OFF

  dispatch_block_t block = ^{

    if (flag)
      self->config &= ~kPreferIPv6;
    else
      self->config |= kPreferIPv6;
  };

  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;
}

- (void) setAlternateAddressDelay:(NSTimeInterval)delay {
  dispatch_block_t block = ^{
    self->alternateAddressDelay = delay;
  };
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_async(socketQueue, block);
}

- (id)userData
{
  __block id result = nil;

  dispatch_block_t block = ^{

    result = self->userData;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  return result;
}

- (void)setUserData:(id)arbitraryUserData
{
  dispatch_block_t block = ^{

    if (self->userData != arbitraryUserData)
    {
      self->userData = arbitraryUserData;
    }
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_async(socketQueue, block);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Accepting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
{
  return [self acceptOnInterface:nil port:port error:errPtr];
}

- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
{
  LogTrace();

  // Just in-case interface parameter is immutable.
  NSString *interface = [inInterface copy];

  __block BOOL result = NO;
  __block NSError *err = nil;

  // CreateSocket Block
  // This block will be invoked within the dispatch block below.

  int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {

    int socketFD = socket(domain, SOCK_STREAM, 0);

    if (socketFD == SOCKET_NULL)
    {
      NSString *reason = @"Error in socket() function";
      err = [self errorWithErrno:errno reason:reason];

      return SOCKET_NULL;
    }

    int status;

    // 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];

      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];

      LogVerbose(@"close(socketFD)");
      close(socketFD);
      return SOCKET_NULL;
    }

    // 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];

      LogVerbose(@"close(socketFD)");
      close(socketFD);
      return SOCKET_NULL;
    }

    // Listen

    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;
    }

    return socketFD;
  };

  // Create dispatch block and run on socketQueue

  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];

      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];

      return_from_block;
    }

    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];

      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];

      return_from_block;
    }

    // Clear queues (spurious read/write requests post disconnect)
    [self->readQueue removeAllObjects];
    [self->writeQueue removeAllObjects];

    // Resolve interface from description

    NSMutableData *interface4 = nil;
    NSMutableData *interface6 = nil;

    [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];

      return_from_block;
    }

    if (isIPv4Disabled && (interface6 == nil))
    {
      NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
      err = [self badParamError:msg];

      return_from_block;
    }

    if (isIPv6Disabled && (interface4 == nil))
    {
      NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
      err = [self badParamError:msg];

      return_from_block;
    }

    BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
    BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);

    // Create sockets, configure, bind, and listen

    if (enableIPv4)
    {
      LogVerbose(@"Creating IPv4 socket");
      self->socket4FD = createSocket(AF_INET, interface4);

      if (self->socket4FD == SOCKET_NULL)
      {
        return_from_block;
      }
    }

    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.

        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
        addr6->sin6_port = htons([self localPort4]);
      }

      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;
        }

        return_from_block;
      }
    }

    // Create accept sources

    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;

      __weak GCDAsyncSocket *weakSelf = 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;

        LogVerbose(@"event4Block");

        unsigned long i = 0;
        unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);

        LogVerbose(@"numPendingConnections: %lu", numPendingConnections);

        while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));

#pragma clang diagnostic pop
      }});


      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

        LogVerbose(@"close(socket4FD)");
        close(socketFD);

#pragma clang diagnostic pop
      });

      LogVerbose(@"dispatch_resume(accept4Source)");
      dispatch_resume(self->accept4Source);
    }

    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;

      __weak GCDAsyncSocket *weakSelf = 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;

        LogVerbose(@"event6Block");

        unsigned long i = 0;
        unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);

        LogVerbose(@"numPendingConnections: %lu", numPendingConnections);

        while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));

#pragma clang diagnostic pop
      }});

      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

        LogVerbose(@"close(socket6FD)");
        close(socketFD);

#pragma clang diagnostic pop
      });

      LogVerbose(@"dispatch_resume(accept6Source)");
      dispatch_resume(self->accept6Source);
    }

    self->flags |= kSocketStarted;

    result = YES;
  }};

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  if (result == NO)
  {
    LogInfo(@"Error in accept: %@", err);

    if (errPtr)
      *errPtr = err;
  }

  return result;
}

- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr
{
  LogTrace();

  __block BOOL result = NO;
  __block NSError *err = nil;

  // CreateSocket Block
  // This block will be invoked within the dispatch block below.

  int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {

    int socketFD = socket(domain, SOCK_STREAM, 0);

    if (socketFD == SOCKET_NULL)
    {
      NSString *reason = @"Error in socket() function";
      err = [self errorWithErrno:errno reason:reason];

      return SOCKET_NULL;
    }

    int status;

    // 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];

      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];

      LogVerbose(@"close(socketFD)");
      close(socketFD);
      return SOCKET_NULL;
    }

    // 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];

      LogVerbose(@"close(socketFD)");
      close(socketFD);
      return SOCKET_NULL;
    }

    // Listen

    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;
    }

    return socketFD;
  };

  // Create dispatch block and run on socketQueue

  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];

      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];

      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];

      return_from_block;
    }

    // Clear queues (spurious read/write requests post disconnect)
    [self->readQueue removeAllObjects];
    [self->writeQueue removeAllObjects];

    // 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];

        return_from_block;
      }
    }

    // Resolve interface from description

    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];

      return_from_block;
    }

    // Create sockets, configure, bind, and listen

    LogVerbose(@"Creating unix domain socket");
    self->socketUN = createSocket(AF_UNIX, interface);

    if (self->socketUN == SOCKET_NULL)
    {
      return_from_block;
    }

    self->socketUrl = url;

    // Create accept sources

    self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                  self->socketUN,
                                                  0,
                                                  self->socketQueue);

    int socketFD = self->socketUN;
    dispatch_source_t acceptSource = self->acceptUNSource;

    __weak GCDAsyncSocket *weakSelf = self;

    dispatch_source_set_event_handler(self->acceptUNSource,
 ^{ @autoreleasepool {

      __strong GCDAsyncSocket *strongSelf = weakSelf;

      LogVerbose(@"eventUNBlock");

      unsigned long i = 0;
      unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);

      LogVerbose(@"numPendingConnections: %lu", numPendingConnections);

      while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
    }});

    dispatch_source_set_cancel_handler(self->acceptUNSource, ^{

#if !OS_OBJECT_USE_OBJC
      LogVerbose(@"dispatch_release(acceptUNSource)");
      dispatch_release(acceptSource);
#endif

      LogVerbose(@"close(socketUN)");
      close(socketFD);
    });

    LogVerbose(@"dispatch_resume(acceptUNSource)");
    dispatch_resume(self->acceptUNSource);

    self->flags |= kSocketStarted;

    result = YES;
  }};

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  if (result == NO)
  {
    LogInfo(@"Error in accept: %@", err);

    if (errPtr)
      *errPtr = err;
  }

  return result;
}

- (BOOL)doAccept:(int)parentSocketFD
{
  LogTrace();

  int socketType;
  int childSocketFD;
  NSData *childSocketAddress;

  if (parentSocketFD == socket4FD)
  {
    socketType = 0;

    struct sockaddr_in addr;
    socklen_t addrLen = sizeof(addr);

    childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);

    if (childSocketFD == -1)
    {
      LogWarn(@"Accept failed with error: %@", [self errnoError]);
      return NO;
    }

    childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
  }
  else if (parentSocketFD == socket6FD)
  {
    socketType = 1;

    struct sockaddr_in6 addr;
    socklen_t addrLen = sizeof(addr);

    childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);

    if (childSocketFD == -1)
    {
      LogWarn(@"Accept failed with error: %@", [self errnoError]);
      return NO;
    }

    childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
  }
  else // if (parentSocketFD == socketUN)
  {
    socketType = 2;

    struct sockaddr_un addr;
    socklen_t addrLen = sizeof(addr);

    childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);

    if (childSocketFD == -1)
    {
      LogWarn(@"Accept failed with error: %@", [self errnoError]);
      return NO;
    }

    childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
  }

  // 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;
  }

  // Prevent SIGPIPE signals

  int nosigpipe = 1;
  setsockopt(childSocketFD,
             SOL_SOCKET,
             SO_NOSIGPIPE,
             &nosigpipe,
             sizeof(nosigpipe));

  // Notify delegate

  if (delegateQueue)
  {
    __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;

    dispatch_async(delegateQueue,
 ^{ @autoreleasepool {

      // Query delegate for custom socket queue

      dispatch_queue_t childSocketQueue = NULL;

      if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
      {
        childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
                                                                      onSocket:self];
      }

      // Create GCDAsyncSocket instance for accepted socket

      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;

      acceptedSocket->flags = (kSocketStarted | kConnected);

      // Setup read and write sources for accepted socket

      dispatch_async(acceptedSocket->socketQueue,
 ^{ @autoreleasepool {

        [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
      }});

      // Notify delegate

      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

      // The accepted socket should have been retained by the delegate.
      // Otherwise it gets properly released when exiting the block.
    }});
  }

  return YES;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Connecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * 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;
}

- (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;

    LogVerbose(@"Dispatching DNS lookup...");

    // 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.

    NSString *hostCpy = [host copy];

    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"

      NSError *lookupErr = nil;
      NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];

      __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;
          }
        }

        dispatch_async(strongSelf->socketQueue,
 ^{ @autoreleasepool {

          [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
        }});
      }

#pragma clang diagnostic pop
    }});

    [self startConnectTimeout:timeout];

    result = YES;
  }};

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);


  if (errPtr) *errPtr = preConnectErr;
  return result;
}

- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)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];
}

- (BOOL)connectToAddress:(NSData *)inRemoteAddr
            viaInterface:(NSString *)inInterface
             withTimeout:(NSTimeInterval)timeout
                   error:(NSError **)errPtr
{
  LogTrace();

  // Just in case immutable objects were passed
  NSData *remoteAddr = [inRemoteAddr copy];
  NSString *interface = [inInterface copy];

  __block BOOL result = NO;
  __block NSError *err = nil;

  dispatch_block_t block = ^{ @autoreleasepool {

    // Check for problems with remoteAddr parameter

    NSData *address4 = nil;
    NSData *address6 = nil;

    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 ((address4 == nil) && (address6 == nil))
    {
      NSString *msg = @"A valid IPv4 or IPv6 address was not given";
      err = [self badParamError:msg];

      return_from_block;
    }

    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];

      return_from_block;
    }

    if (isIPv6Disabled && (address6 != nil))
    {
      NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
      err = [self badParamError:msg];

      return_from_block;
    }

    // Run through standard pre-connect checks

    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.

    if (![self connectWithAddress4:address4 address6:address6 error:&err])
    {
      return_from_block;
    }

    self->flags |= kSocketStarted;

    [self startConnectTimeout:timeout];

    result = YES;
  }};

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  if (result == NO)
  {
    if (errPtr)
      *errPtr = err;
  }

  return result;
}

- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
{
  LogTrace();

  __block BOOL result = NO;
  __block NSError *err = nil;

  dispatch_block_t block = ^{ @autoreleasepool {

    // Check for problems with host parameter

    if ([url.path length] == 0)
    {
      NSString *msg = @"Invalid unix domain socket url.";
      err = [self badParamError:msg];

      return_from_block;
    }

    // Run through standard pre-connect checks

    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.

    self->flags |= kSocketStarted;

    // Start the normal connection process

    NSError *connectError = nil;
    if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError])
    {
      [self closeWithError:connectError];

      return_from_block;
    }

    [self startConnectTimeout:timeout];

    result = YES;
  }};

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  if (result == NO)
  {
    if (errPtr)
      *errPtr = err;
  }

  return result;
}

- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
  LogTrace();

  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");

    // The connect operation has been cancelled.
    // That is, socket was disconnected, or connection has already timed out.
    return;
  }

  // Check for problems

  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.";

    [self closeWithError:[self otherError:msg]];
    return;
  }

  if (isIPv6Disabled && (address4 == nil))
  {
    NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";

    [self closeWithError:[self otherError:msg]];
    return;
  }

  // Start the normal connection process

  NSError *err = nil;
  if (![self connectWithAddress4:address4 address6:address6 error:&err])
  {
    [self closeWithError:err];
  }
}

/**
 * This method is called if the DNS lookup fails.
 * This method is executed on the socketQueue.
 *
 * 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();

  NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),
           @"Must be dispatched on socketQueue");


  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;
  }

  [self endConnectTimeout];
  [self closeWithError:error];
}

- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
{
  // Bind the socket to the desired interface (if needed)

  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.

      int reuseOn = 1;
      setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
    }

    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"];

      return NO;
    }
  }

  return YES;
}

- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
  int socketFD = socket(family, SOCK_STREAM, 0);

  if (socketFD == SOCKET_NULL)
  {
    if (errPtr)
      *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];

    return socketFD;
  }

  if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
  {
    [self closeSocket:socketFD];

    return SOCKET_NULL;
  }

  // Prevent SIGPIPE signals

  int nosigpipe = 1;
  setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));

  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;
  }

  // Start the connection process in a background queue

  __weak GCDAsyncSocket *weakSelf = self;

  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;

    __strong GCDAsyncSocket *strongSelf = weakSelf;
    if (strongSelf == nil) return_from_block;

    dispatch_async(strongSelf->socketQueue,
 ^{ @autoreleasepool {

      if (strongSelf.isConnected)
      {
        [strongSelf closeSocket:socketFD];
        return_from_block;
      }

      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];
        }
      }
    }});

#pragma clang diagnostic pop
  });

  LogVerbose(@"Connecting...");
}

- (void)closeSocket:(int)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;
    }
  }
}

- (void)closeUnusedSocket:(int)usedSocketFD
{
  if (usedSocketFD != socket4FD)
  {
    [self closeSocket:socket4FD];
  }
  else if (usedSocketFD != socket6FD)
  {
    [self closeSocket:socket6FD];
  }
}

- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
  LogTrace();

  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]);

  // Determine socket type

  BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;

  // Create and bind the sockets

  if (address4)
  {
    LogVerbose(@"Creating IPv4 socket");

    socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
  }

  if (address6)
  {
    LogVerbose(@"Creating IPv6 socket");

    socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
  }

  if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
  {
    return NO;
  }

  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;
  }

  int aStateIndex = stateIndex;

  [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];
    });
  }

  return YES;
}

- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
{
  LogTrace();

  NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),
           @"Must be dispatched on socketQueue");

  // Create the socket

  int socketFD;

  LogVerbose(@"Creating unix domain socket");

  socketUN = socket(AF_UNIX, SOCK_STREAM, 0);

  socketFD = socketUN;

  if (socketFD == SOCKET_NULL)
  {
    if (errPtr)
      *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];

    return NO;
  }

  // Bind the socket to the desired interface (if needed)

  LogVerbose(@"Binding socket...");

  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;
  //	}

  // Prevent SIGPIPE signals

  int nosigpipe = 1;
  setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));

  // Start the connection process in a background queue

  int aStateIndex = stateIndex;

  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 {

        [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 {

        [self didNotConnect:aStateIndex error:error];
      }});
    }
  });

  LogVerbose(@"Connecting...");

  return YES;
}

- (void)didConnect:(int)aStateIndex
{
  LogTrace();

  NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),
           @"Must be dispatched on socketQueue");


  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;
  }

  flags |= kConnected;

  [self endConnectTimeout];

#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.

  dispatch_block_t SetupStreamsPart1 = ^{
#if TARGET_OS_IPHONE

    if (![self createReadAndWriteStream])
    {
      [self closeWithError:[self otherError:@"Error creating CFStreams"]];
      return;
    }

    if (![self registerForStreamCallbacksIncludingReadWrite:NO])
    {
      [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
      return;
    }

#endif
  };
  dispatch_block_t SetupStreamsPart2 = ^{
#if TARGET_OS_IPHONE

    if (aStateIndex != self->stateIndex)
    {
      // The socket has been disconnected.
      return;
    }

    if (![self addStreamsToRunLoop])
    {
      [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
      return;
    }

    if (![self openStreams])
    {
      [self closeWithError:[self otherError:@"Error creating CFStreams"]];
      return;
    }

#endif
  };

  // Notify delegate

  NSString *host = [self connectedHost];
  uint16_t port = [self connectedPort];
  NSURL *url = [self connectedUrl];

  __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;

  if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
  {
    SetupStreamsPart1();

    dispatch_async(delegateQueue, ^{ @autoreleasepool {

      [theDelegate socket:self didConnectToHost:host port:port];

      dispatch_async(self->socketQueue, ^{ @autoreleasepool {

        SetupStreamsPart2();
      }});
    }});
  }
  else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
  {
    SetupStreamsPart1();

    dispatch_async(delegateQueue, ^{ @autoreleasepool {

      [theDelegate socket:self didConnectToUrl:url];

      dispatch_async(self->socketQueue, ^{ @autoreleasepool {

        SetupStreamsPart2();
      }});
    }});
  }
  else
  {
    SetupStreamsPart1();
    SetupStreamsPart2();
  }

  // Get the connected socket

  int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;

  // 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]];

    return;
  }

  // Setup our read/write sources

  [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];

  // Dequeue any pending read/write requests

  [self maybeDequeueRead];
  [self maybeDequeueWrite];
}

- (void)didNotConnect:(int)aStateIndex error:(NSError *)error
{
  LogTrace();

  NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),
           @"Must be dispatched on socketQueue");


  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;
  }

  [self closeWithError:error];
}

- (void)startConnectTimeout:(NSTimeInterval)timeout
{
  if (timeout >= 0.0)
  {
    connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                          0,
                                          0,
                                          socketQueue);

    __weak GCDAsyncSocket *weakSelf = 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;

      [strongSelf doConnectTimeout];

#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"

      LogVerbose(@"dispatch_release(connectTimer)");
      dispatch_release(theConnectTimer);

#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_resume(connectTimer);
  }
}

- (void)endConnectTimeout
{
  LogTrace();

  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.

  stateIndex++;

  if (connectInterface4)
  {
    connectInterface4 = nil;
  }
  if (connectInterface6)
  {
    connectInterface6 = nil;
  }
}

- (void)doConnectTimeout
{
  LogTrace();

  [self endConnectTimeout];
  [self closeWithError:[self connectTimeoutError]];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Disconnecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (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<GCDAsyncSocketDelegate> 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};

  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};

  return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo];
}

- (NSError *)errnoError
{
  NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  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};

  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);

  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  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);

  NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};

  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);

  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  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);

  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
}

- (NSError *)connectionClosedError
{
  NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
                                                       @"GCDAsyncSocket",
                                                       [NSBundle mainBundle],
                                                       @"Socket closed by remote peer",
                                                       nil);

  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
}

- (NSError *)otherError:(NSString *)errMsg
{
  NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};

  return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Diagnostics
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)isDisconnected
{
  __block BOOL result = NO;

  dispatch_block_t block = ^{
    result = (self->flags & kSocketStarted) ? NO : YES;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  return result;
}

- (BOOL)isConnected
{
  __block BOOL result = NO;

  dispatch_block_t block = ^{
    result = (self->flags & kConnected) ? YES : NO;
  };

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  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];

    return nil;
  }
  else
  {
    __block NSString *result = nil;

    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];
    }});

    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];

    return 0;
  }
  else
  {
    __block uint16_t result = 0;

    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];
    });

    return result;
  }
}

- (NSURL *)connectedUrl
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    if (socketUN != SOCKET_NULL)
      return [self connectedUrlFromSocketUN:socketUN];

    return nil;
  }
  else
  {
    __block NSURL *result = nil;

    dispatch_sync(socketQueue, ^{ @autoreleasepool {

      if (self->socketUN != SOCKET_NULL)
        result = [self connectedUrlFromSocketUN:self->socketUN];
    }});

    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];

    return nil;
  }
  else
  {
    __block NSString *result = nil;

    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];
    }});

    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];

    return 0;
  }
  else
  {
    __block uint16_t result = 0;

    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];
    });

    return result;
  }
}

- (NSString *)connectedHost4
{
  if (socket4FD != SOCKET_NULL)
    return [self connectedHostFromSocket4:socket4FD];

  return nil;
}

- (NSString *)connectedHost6
{
  if (socket6FD != SOCKET_NULL)
    return [self connectedHostFromSocket6:socket6FD];

  return nil;
}

- (uint16_t)connectedPort4
{
  if (socket4FD != SOCKET_NULL)
    return [self connectedPortFromSocket4:socket4FD];

  return 0;
}

- (uint16_t)connectedPort6
{
  if (socket6FD != SOCKET_NULL)
    return [self connectedPortFromSocket6:socket6FD];

  return 0;
}

- (NSString *)localHost4
{
  if (socket4FD != SOCKET_NULL)
    return [self localHostFromSocket4:socket4FD];

  return nil;
}

- (NSString *)localHost6
{
  if (socket6FD != SOCKET_NULL)
    return [self localHostFromSocket6:socket6FD];

  return nil;
}

- (uint16_t)localPort4
{
  if (socket4FD != SOCKET_NULL)
    return [self localPortFromSocket4:socket4FD];

  return 0;
}

- (uint16_t)localPort6
{
  if (socket6FD != SOCKET_NULL)
    return [self localPortFromSocket6:socket6FD];

  return 0;
}

- (NSString *)connectedHostFromSocket4:(int)socketFD
{
  struct sockaddr_in sockaddr4;
  socklen_t sockaddr4len = sizeof(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);

  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);

  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);

  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);

  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);

  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);

  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);

  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);

  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];
      }
    }

    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);

  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);

      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);

      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);

  return result;
}

- (BOOL)isIPv4
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return (socket4FD != SOCKET_NULL);
  }
  else
  {
    __block BOOL result = NO;

    dispatch_sync(socketQueue, ^{
      result = (self->socket4FD != SOCKET_NULL);
    });

    return result;
  }
}

- (BOOL)isIPv6
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return (socket6FD != SOCKET_NULL);
  }
  else
  {
    __block BOOL result = NO;

    dispatch_sync(socketQueue, ^{
      result = (self->socket6FD != SOCKET_NULL);
    });

    return result;
  }
}

- (BOOL)isSecure
{
  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return (flags & kSocketSecure) ? YES : NO;
  }
  else
  {
    __block BOOL result;

    dispatch_sync(socketQueue, ^{
      result = (self->flags & kSocketSecure) ? YES : NO;
    });

    return result;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Finds the address of an interface description.
 * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
 *
 * The interface description may optionally contain a port number at the end, separated by a colon.
 * 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

          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);

            addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
          }
          else
          {
            char ip[INET6_ADDRSTRLEN];

            const char *conversion = inet_ntop(AF_INET6,
                                               &nativeAddr6.sin6_addr,
                                               ip,
                                               sizeof(ip));

            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;
}

- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url
{
  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)];

  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);

  // Setup event handlers

  __weak GCDAsyncSocket *weakSelf = 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;

    LogVerbose(@"readEventBlock");

    strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
    LogVerbose(@"socketFDBytesAvailable: %lu",
               strongSelf->socketFDBytesAvailable);

    if (strongSelf->socketFDBytesAvailable > 0)
      [strongSelf doReadData];
    else
      [strongSelf doReadEOF];

#pragma clang diagnostic pop
  }});

  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;

    LogVerbose(@"writeEventBlock");

    strongSelf->flags |= kSocketCanAcceptBytes;
    [strongSelf doWriteData];

#pragma clang diagnostic pop
  }});

  // Setup cancel handlers

  __block int socketFDRefCount = 2;

#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"

    LogVerbose(@"readCancelBlock");

#if !OS_OBJECT_USE_OBJC
    LogVerbose(@"dispatch_release(readSource)");
    dispatch_release(theReadSource);
#endif

    if (--socketFDRefCount == 0)
    {
      LogVerbose(@"close(socketFD)");
      close(socketFD);
    }

#pragma clang diagnostic pop
  });

  dispatch_source_set_cancel_handler(writeSource, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

    LogVerbose(@"writeCancelBlock");

#if !OS_OBJECT_USE_OBJC
    LogVerbose(@"dispatch_release(writeSource)");
    dispatch_release(theWriteSource);
#endif

    if (--socketFDRefCount == 0)
    {
      LogVerbose(@"close(socketFD)");
      close(socketFD);
    }

#pragma clang diagnostic pop
  });

  // We will not be able to read until data arrives.
  // But we should be able to write immediately.

  socketFDBytesAvailable = 0;
  flags &= ~kReadSourceSuspended;

  LogVerbose(@"dispatch_resume(readSource)");
  dispatch_resume(readSource);

  flags |= kSocketCanAcceptBytes;
  flags |= kWriteSourceSuspended;
}

- (BOOL)usingCFStreamForTLS
{
#if TARGET_OS_IPHONE

  if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
  {
    // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.

    return YES;
  }

#endif

  return NO;
}

- (BOOL)usingSecureTransportForTLS
{
  // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable)

#if TARGET_OS_IPHONE

  if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
  {
    // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.

    return NO;
  }

#endif

  return YES;
}

- (void)suspendReadSource
{
  if (!(flags & kReadSourceSuspended))
  {
    LogVerbose(@"dispatch_suspend(readSource)");

    dispatch_suspend(readSource);
    flags |= kReadSourceSuspended;
  }
}

- (void)resumeReadSource
{
  if (flags & kReadSourceSuspended)
  {
    LogVerbose(@"dispatch_resume(readSource)");

    dispatch_resume(readSource);
    flags &= ~kReadSourceSuspended;
  }
}

- (void)suspendWriteSource
{
  if (!(flags & kWriteSourceSuspended))
  {
    LogVerbose(@"dispatch_suspend(writeSource)");

    dispatch_suspend(writeSource);
    flags |= kWriteSourceSuspended;
  }
}

- (void)resumeWriteSource
{
  if (flags & kWriteSourceSuspended)
  {
    LogVerbose(@"dispatch_resume(writeSource)");

    dispatch_resume(writeSource);
    flags &= ~kWriteSourceSuspended;
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Reading
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
{
  [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}

- (void)readDataWithTimeout:(NSTimeInterval)timeout
                     buffer:(NSMutableData *)buffer
               bufferOffset:(NSUInteger)offset
                        tag:(long)tag
{
  [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
}

- (void)readDataWithTimeout:(NSTimeInterval)timeout
                     buffer:(NSMutableData *)buffer
               bufferOffset:(NSUInteger)offset
                  maxLength:(NSUInteger)length
                        tag:(long)tag
{
  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];

  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.
}

- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
  [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
}

- (void)readDataToLength:(NSUInteger)length
             withTimeout:(NSTimeInterval)timeout
                  buffer:(NSMutableData *)buffer
            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;
  }

  GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
                                                            startOffset:offset
                                                              maxLength:0
                                                                timeout:timeout
                                                             readLength:length
                                                             terminator:nil
                                                                    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.
}

- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
  [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}

- (void)readDataToData:(NSData *)data
           withTimeout:(NSTimeInterval)timeout
                buffer:(NSMutableData *)buffer
          bufferOffset:(NSUInteger)offset
                   tag:(long)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];
}

- (void)readDataToData:(NSData *)data
           withTimeout:(NSTimeInterval)timeout
                buffer:(NSMutableData *)buffer
          bufferOffset:(NSUInteger)offset
             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.
}

- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
{
  __block float result = 0.0F;

  dispatch_block_t block = ^{

    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;

      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;

      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 (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
    block();
  else
    dispatch_sync(socketQueue, block);

  return result;
}

/**
 * This method starts a new read, if needed.
 *
 * It is called when:
 * - a user requests a read
 * - after a read 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)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];
        }
      }
    }
  }
}

- (void)flushSSLBuffers
{
  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

      // 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.

            currentRead->bytesDone += bytesRead;
            totalBytesReadForCurrentRead += bytesRead;
            done = NO;
          }
        }

        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

        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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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];
            }

            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<GCDAsyncSocketDelegate> 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.
}

- (void)completeCurrentWrite
{
  LogTrace();

  NSAssert(currentWrite,
           @"Trying to complete current write when there is no current write.");


  __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;

  if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
  {
    long theWriteTag = currentWrite->tag;

    dispatch_async(delegateQueue, ^{ @autoreleasepool {

      [theDelegate socket:self didWriteDataWithTag:theWriteTag];
    }});
  }

  [self endCurrentWrite];
}

- (void)endCurrentWrite
{
  if (writeTimer)
  {
    dispatch_source_cancel(writeTimer);
    writeTimer = NULL;
  }

  currentWrite = nil;
}

- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
{
  if (timeout >= 0.0)
  {
    writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                        0,
                                        0,
                                        socketQueue);

    __weak GCDAsyncSocket *weakSelf = self;

    dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

      __strong GCDAsyncSocket *strongSelf = weakSelf;
      if (strongSelf == nil) return_from_block;

      [strongSelf doWriteTimeout];

#pragma clang diagnostic pop
    }});

#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"

      LogVerbose(@"dispatch_release(writeTimer)");
      dispatch_release(theWriteTimer);

#pragma clang diagnostic pop
    });
#endif

    dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW,
                                       (int64_t)(timeout * NSEC_PER_SEC));

    dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
    dispatch_resume(writeTimer);
  }
}

- (void)doWriteTimeout
{
  // 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 |= kWritesPaused;

  __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;

  if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
  {
    GCDAsyncWritePacket *theWrite = currentWrite;

    dispatch_async(delegateQueue, ^{ @autoreleasepool {

      NSTimeInterval timeoutExtension = 0.0;

      timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
                                     elapsed:theWrite->timeout
                                   bytesDone:theWrite->bytesDone];

      dispatch_async(self->socketQueue, ^{ @autoreleasepool {

        [self doWriteTimeoutWithExtension:timeoutExtension];
      }});
    }});
  }
  else
  {
    [self doWriteTimeoutWithExtension:0.0];
  }
}

- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
  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);

      // Unpause writes, and continue
      flags &= ~kWritesPaused;
      [self doWriteData];
    }
    else
    {
      LogVerbose(@"WriteTimeout");

      [self closeWithError:[self writeTimeoutError]];
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)startTLS:(NSDictionary *)tlsSettings
{
  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.

    tlsSettings = [NSDictionary dictionary];
  }

  GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];

  dispatch_async(socketQueue,
 ^{ @autoreleasepool {

    if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites))
    {
      [self->readQueue addObject:packet];
      [self->writeQueue addObject:packet];

      self->flags |= kQueuedTLS;

      [self maybeDequeueRead];
      [self maybeDequeueWrite];
    }
  }});

}

- (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
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security via SecureTransport
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (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);

    // 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.

    [self resumeReadSource];

    *bufferLength = 0;
    return errSSLWouldBlock;
  }

  size_t totalBytesRead = 0;
  size_t totalBytesLeftToBeRead = *bufferLength;

  BOOL done = NO;
  BOOL socketError = NO;

  //
  // STEP 1 : READ FROM SSL PRE BUFFER
  //

  size_t sslPreBufferLength = [sslPreBuffer availableBytes];

  if (sslPreBufferLength > 0)
  {
    LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);

    size_t bytesToCopy;
    if (sslPreBufferLength > totalBytesLeftToBeRead)
      bytesToCopy = totalBytesLeftToBeRead;
    else
      bytesToCopy = sslPreBufferLength;

    LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer",
               THIS_METHOD,
               bytesToCopy);

    memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
    [sslPreBuffer didRead:bytesToCopy];

    LogVerbose(@"%@: sslPreBuffer.length = %zu",
               THIS_METHOD,
               [sslPreBuffer availableBytes]);

    totalBytesRead += bytesToCopy;
    totalBytesLeftToBeRead -= bytesToCopy;

    done = (totalBytesLeftToBeRead == 0);

    if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
  }

  //
  // STEP 2 : READ FROM SOCKET
  //

  if (!done && (socketFDBytesAvailable > 0))
  {
    LogVerbose(@"%@: Reading from socket...", THIS_METHOD);

    int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;

    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.

      LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);

      [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];

      readIntoPreBuffer = YES;
      bytesToRead = (size_t)socketFDBytesAvailable;
      buf = [sslPreBuffer writeBuffer];
    }
    else
    {
      // Read available data from socket directly into dataBuffer.

      LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);

      readIntoPreBuffer = NO;
      bytesToRead = totalBytesLeftToBeRead;
      buf = (uint8_t *)buffer + totalBytesRead;
    }

    ssize_t result = read(socketFD, buf, bytesToRead);
    LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);

    if (result < 0)
    {
      LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);

      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];

        size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);

        LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer",
                   THIS_METHOD,
                   bytesToCopy);

        memcpy((uint8_t *)buffer + totalBytesRead,
               [sslPreBuffer readBuffer],
               bytesToCopy);
        [sslPreBuffer didRead:bytesToCopy];

        totalBytesRead += bytesToCopy;
        totalBytesLeftToBeRead -= bytesToCopy;

        LogVerbose(@"%@: sslPreBuffer.length = %zu",
                   THIS_METHOD,
                   [sslPreBuffer availableBytes]);
      }
      else
      {
        totalBytesRead += bytesReadFromSocket;
        totalBytesLeftToBeRead -= bytesReadFromSocket;
      }

      done = (totalBytesLeftToBeRead == 0);

      if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
    }
  }

  *bufferLength = totalBytesRead;

  if (done)
    return noErr;

  if (socketError)
    return errSSLClosedAbort;

  return errSSLWouldBlock;
}

- (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.

    [self resumeWriteSource];

    *bufferLength = 0;
    return errSSLWouldBlock;
  }

  size_t bytesToWrite = *bufferLength;
  size_t bytesWritten = 0;

  BOOL done = NO;
  BOOL socketError = NO;

  int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;

  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);
  }

  *bufferLength = bytesWritten;

  if (done)
    return noErr;

  if (socketError)
    return errSSLClosedAbort;

  return errSSLWouldBlock;
}

static OSStatus SSLReadFunction(SSLConnectionRef connection,
                                void *data,
                                size_t *dataLength)
{
  GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;

  NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey),
            @"What the deuce?");

  return [asyncSocket sslReadWithBuffer:data length:dataLength];
}

static OSStatus SSLWriteFunction(SSLConnectionRef connection,
                                 const void *data,
                                 size_t *dataLength)
{
  GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;

  NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey),
            @"What the deuce?");

  return [asyncSocket sslWriteWithBuffer:data length:dataLength];
}

- (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;
    }

  }

  // 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

    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]])
  {
    CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value);
    status = SSLSetALPNProtocols(sslContext, protocols);
    if (status != noErr)
    {
      [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]];
      return;
    }
  }
  else if (value)
  {
    NSAssert(NO,
             @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray.");

    [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]];
    return;
  }

  // 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;
  }

  // 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 closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]];
    return;
  }

  // 14. kCFStreamSSLLevel

#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");

    [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]];
    return;
  }

  // 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.

  sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];

  size_t preBufferLength  = [preBuffer availableBytes];

  if (preBufferLength > 0)
  {
    [sslPreBuffer ensureCapacityForWrite:preBufferLength];

    memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
    [preBuffer didRead:preBufferLength];
    [sslPreBuffer didWrite:preBufferLength];
  }

  sslErrCode = lastSSLHandshakeError = noErr;

  // Start the SSL Handshake process

  [self ssl_continueSSLHandshake];
}

- (void)ssl_continueSSLHandshake
{
  LogTrace();

  // 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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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]];
  }
}

- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex
{
  LogTrace();

  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

    return;
  }

  // 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]];
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security via CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if TARGET_OS_IPHONE

- (void)cf_finishSSLHandshake
{
  LogTrace();

  if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
  {
    flags &= ~kStartingReadTLS;
    flags &= ~kStartingWriteTLS;

    flags |= kSocketSecure;

    __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;

    if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
    {
      dispatch_async(delegateQueue, ^{ @autoreleasepool {

        [theDelegate socketDidSecure:self];
      }});
    }

    [self endCurrentRead];
    [self endCurrentWrite];

    [self maybeDequeueRead];
    [self maybeDequeueWrite];
  }
}

- (void)cf_abortSSLHandshake:(NSError *)error
{
  LogTrace();

  if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
  {
    flags &= ~kStartingReadTLS;
    flags &= ~kStartingWriteTLS;

    [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...");
}

#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if TARGET_OS_IPHONE

+ (void)ignore:(id)_
{}

+ (void)startCFStreamThreadIfNeeded
{
  LogTrace();

  static dispatch_once_t predicate;
  dispatch_once(&predicate,
 ^{

    cfstreamThreadRetainCount = 0;
    cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup",
                                                     DISPATCH_QUEUE_SERIAL);
  });

  dispatch_sync(cfstreamThreadSetupQueue,
 ^{ @autoreleasepool {

    if (++cfstreamThreadRetainCount == 1)
    {
      cfstreamThread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(cfstreamThread:)
                                                 object:nil];
      [cfstreamThread start];
    }
  }});
}

+ (void)stopCFStreamThreadIfNeeded
{
  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.

  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)
    {
      [cfstreamThread cancel]; // set isCancelled flag

      // wake up the thread
      [[self class] performSelector:@selector(ignore:)
                           onThread:cfstreamThread
                         withObject:[NSNull null]
                      waitUntilDone:NO];

      cfstreamThread = nil;
    }

#pragma clang diagnostic pop
  }});
}

+ (void)cfstreamThread:(id)unused { @autoreleasepool
  {
    [[NSThread currentThread] setName:GCDAsyncSocketThreadName];

    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];

    NSThread *currentThread = [NSThread currentThread];
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];

    BOOL isCancelled = [currentThread isCancelled];

    while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
    {
      isCancelled = [currentThread isCancelled];
    }

    LogInfo(@"CFStreamThread: Stopped");
  }}

+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
  LogTrace();
  NSAssert([NSThread currentThread] == cfstreamThread,
           @"Invoked on wrong thread");

  CFRunLoopRef runLoop = CFRunLoopGetCurrent();

  if (asyncSocket->readStream)
    CFReadStreamScheduleWithRunLoop(asyncSocket->readStream,
                                    runLoop,
                                    kCFRunLoopDefaultMode);

  if (asyncSocket->writeStream)
    CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream,
                                     runLoop,
                                     kCFRunLoopDefaultMode);
}

+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
  LogTrace();
  NSAssert([NSThread currentThread] == cfstreamThread,
           @"Invoked on wrong thread");

  CFRunLoopRef runLoop = CFRunLoopGetCurrent();

  if (asyncSocket->readStream)
    CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream,
                                      runLoop,
                                      kCFRunLoopDefaultMode);

  if (asyncSocket->writeStream)
    CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream,
                                       runLoop,
                                       kCFRunLoopDefaultMode);
}

static void CFReadStreamCallback (CFReadStreamRef stream,
                                  CFStreamEventType type,
                                  void *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;
    }
  }
#pragma clang diagnostic pop
}

static void CFWriteStreamCallback (CFWriteStreamRef stream,
                                   CFStreamEventType type,
                                   void *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;
    }
  }
#pragma clang diagnostic pop
}

- (BOOL)createReadAndWriteStream
{
  LogTrace();

  NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey),
           @"Must be dispatched on socketQueue");


  if (readStream || writeStream)
  {
    // Streams already created
    return YES;
  }

  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 (![self isConnected])
  {
    // Cannot create streams until file descriptor is connected
    return NO;
  }

  LogVerbose(@"Creating read and write stream...");

  CFStreamCreatePairWithSocket(NULL,
                               (CFSocketNativeHandle)socketFD,
                               &readStream,
                               &writeStream);

  // 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 == 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;
    }

    return NO;
  }

  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;
  }

  CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
  if (includeReadWrite)
    writeStreamEvents |= kCFStreamEventCanAcceptBytes;

  if (!CFWriteStreamSetClient(writeStream,
                              writeStreamEvents,
                              &CFWriteStreamCallback,
                              &streamContext))
  {
    return NO;
  }

  return YES;
}

- (BOOL)addStreamsToRunLoop
{
  LogTrace();

  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...");

    [[self class] startCFStreamThreadIfNeeded];
    dispatch_sync(cfstreamThreadSetupQueue, ^{
      [[self class] performSelector:@selector(scheduleCFStreams:)
                           onThread:cfstreamThread
                         withObject:self
                      waitUntilDone:YES];
    });
    flags |= kAddedStreamsToRunLoop;
  }

  return YES;
}

- (void)removeStreamsFromRunLoop
{
  LogTrace();

  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...");

    dispatch_sync(cfstreamThreadSetupQueue, ^{
      [[self class] performSelector:@selector(unscheduleCFStreams:)
                           onThread:cfstreamThread
                         withObject:self
                      waitUntilDone:YES];
    });
    [[self class] stopCFStreamThreadIfNeeded];

    flags &= ~kAddedStreamsToRunLoop;
  }
}

- (BOOL)openStreams
{
  LogTrace();

  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);

  if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
  {
    LogVerbose(@"Opening read and write stream...");

    BOOL r1 = CFReadStreamOpen(readStream);
    BOOL r2 = CFWriteStreamOpen(writeStream);

    if (!r1 || !r2)
    {
      LogError(@"Error in CFStreamOpen");
      return NO;
    }
  }

  return YES;
}

#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Advanced
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * See header file for big discussion of this method.
 **/
- (BOOL)autoDisconnectOnClosedReadStream
{
  // Note: YES means kAllowHalfDuplexConnection is OFF

  if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    return ((config & kAllowHalfDuplexConnection) == 0);
  }
  else
  {
    __block BOOL result;

    dispatch_sync(socketQueue, ^{
      result = ((self->config & kAllowHalfDuplexConnection) == 0);
    });

    return result;
  }
}

/**
 * See header file for big discussion of this method.
 **/
- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
{
  // Note: YES means kAllowHalfDuplexConnection is OFF

  dispatch_block_t block = ^{

    if (flag)
      self->config &= ~kAllowHalfDuplexConnection;
    else
      self->config |= kAllowHalfDuplexConnection;
  };

  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);
}

/**
 * See header file for big discussion of this method.
 **/
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
{
  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);
}

/**
 * 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 (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;
  }

  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;
  }

  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 (readStream == NULL)
    [self createReadAndWriteStream];

  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 (writeStream == NULL)
    [self createReadAndWriteStream];

  return writeStream;
}

- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
{
  if (![self createReadAndWriteStream])
  {
    // Error occurred creating streams (perhaps socket isn't open)
    return NO;
  }

  BOOL r1, r2;

  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);
#pragma clang diagnostic pop

  if (!r1 || !r2)
  {
    return NO;
  }

  if (!caveat)
  {
    if (![self openStreams])
    {
      return NO;
    }
  }

  return YES;
}

/**
 * Questions? Have you read the header file?
 **/
- (BOOL)enableBackgroundingOnSocket
{
  LogTrace();

  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];
}

- (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...

  LogTrace();

  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];
}

#endif

- (SSLContextRef)sslContext
{
  if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  {
    LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation",
            THIS_METHOD);
    return NULL;
  }

  return sslContext;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

+ (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;
}

+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
  char addrBuf[INET_ADDRSTRLEN];

  if (inet_ntop(AF_INET,
                &pSockaddr4->sin_addr,
                addrBuf,
                (socklen_t)sizeof(addrBuf)) == NULL)
  {
    addrBuf[0] = '\0';
  }

  return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}

+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
  char addrBuf[INET6_ADDRSTRLEN];

  if (inet_ntop(AF_INET6,
                &pSockaddr6->sin6_addr,
                addrBuf,
                (socklen_t)sizeof(addrBuf)) == NULL)
  {
    addrBuf[0] = '\0';
  }

  return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}

+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
  return ntohs(pSockaddr4->sin_port);
}

+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
  return ntohs(pSockaddr6->sin6_port);
}

+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
{
  NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
  return [NSURL fileURLWithPath:path];
}

+ (NSString *)hostFromAddress:(NSData *)address
{
  NSString *host;

  if ([self getHost:&host port:NULL fromAddress:address])
    return host;
  else
    return nil;
}

+ (uint16_t)portFromAddress:(NSData *)address
{
  uint16_t port;

  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 = (const struct sockaddr *)(const void *)[address bytes];

    if (sockaddrX->sa_family == AF_INET) {
      return YES;
    }
  }

  return NO;
}

+ (BOOL)isIPv6Address:(NSData *)address
{
  if ([address length] >= sizeof(struct sockaddr))
  {
    const struct sockaddr *sockaddrX = (const struct sockaddr *)(const void *)[address bytes];

    if (sockaddrX->sa_family == AF_INET6) {
      return YES;
    }
  }

  return NO;
}

+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)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 = (const struct sockaddr *)(const void *)[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 (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));

        if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
        if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
        if (afPtr)   *afPtr   = AF_INET6;

        return YES;
      }
    }
  }

  return NO;
}

+ (NSData *)CRLFData
{
  return [NSData dataWithBytes:"\x0D\x0A" length:2];
}

+ (NSData *)CRData
{
  return [NSData dataWithBytes:"\x0D" length:1];
}

+ (NSData *)LFData
{
  return [NSData dataWithBytes:"\x0A" length:1];
}

+ (NSData *)ZeroData
{
  return [NSData dataWithBytes:"" length:1];
}

@end

#pragma clang diagnostic pop
