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

#import "GCDAsyncUdpSocket.h"

#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

#if TARGET_OS_IPHONE
  #import <CFNetwork/CFNetwork.h>
  #import <UIKit/UIKit.h>
#endif

#import <arpa/inet.h>
#import <fcntl.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <net/if.h>
#import <sys/socket.h>
#import <sys/types.h>


#if 0

// Logging Enabled - See log level below

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

#define LogAsync   NO
#define LogContext 65535

#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__)

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

#else

// Logging Disabled

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

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

#define LogTrace()              {}
#define LogCTrace(frmt, ...)    {}

#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

/**
 * Just to type less code.
**/
#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} 


@class GCDAsyncUdpSendPacket;

NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException";
NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain";

NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket";
NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream";

enum GCDAsyncUdpSocketFlags
{
	kDidCreateSockets        = 1 <<  0,  // If set, the sockets have been created.
	kDidBind                 = 1 <<  1,  // If set, bind has been called.
	kConnecting              = 1 <<  2,  // If set, a connection attempt is in progress.
	kDidConnect              = 1 <<  3,  // If set, socket is connected.
	kReceiveOnce             = 1 <<  4,  // If set, one-at-a-time receive is enabled
	kReceiveContinuous       = 1 <<  5,  // If set, continuous receive is enabled
	kIPv4Deactivated         = 1 <<  6,  // If set, socket4 was closed due to bind or connect on IPv6.
	kIPv6Deactivated         = 1 <<  7,  // If set, socket6 was closed due to bind or connect on IPv4.
	kSend4SourceSuspended    = 1 <<  8,  // If set, send4Source is suspended.
	kSend6SourceSuspended    = 1 <<  9,  // If set, send6Source is suspended.
	kReceive4SourceSuspended = 1 << 10,  // If set, receive4Source is suspended.
	kReceive6SourceSuspended = 1 << 11,  // If set, receive6Source is suspended.
	kSock4CanAcceptBytes     = 1 << 12,  // If set, we know socket4 can accept bytes. If unset, it's unknown.
	kSock6CanAcceptBytes     = 1 << 13,  // If set, we know socket6 can accept bytes. If unset, it's unknown.
	kForbidSendReceive       = 1 << 14,  // If set, no new send or receive operations are allowed to be queued.
	kCloseAfterSends         = 1 << 15,  // If set, close as soon as no more sends are queued.
	kFlipFlop                = 1 << 16,  // Used to alternate between IPv4 and IPv6 sockets.
#if TARGET_OS_IPHONE
	kAddedStreamListener     = 1 << 17,  // If set, CFStreams have been added to listener thread
#endif
};

enum GCDAsyncUdpSocketConfig
{
	kIPv4Disabled  = 1 << 0,  // If set, IPv4 is disabled
	kIPv6Disabled  = 1 << 1,  // If set, IPv6 is disabled
	kPreferIPv4    = 1 << 2,  // If set, IPv4 is preferred over IPv6
	kPreferIPv6    = 1 << 3,  // If set, IPv6 is preferred over IPv4
};

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

@interface GCDAsyncUdpSocket ()
{
#if __has_feature(objc_arc_weak)
	__weak id delegate;
#else
	__unsafe_unretained id delegate;
#endif
	dispatch_queue_t delegateQueue;
	
	GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock;
	dispatch_queue_t receiveFilterQueue;
	BOOL receiveFilterAsync;
	
	GCDAsyncUdpSocketSendFilterBlock sendFilterBlock;
	dispatch_queue_t sendFilterQueue;
	BOOL sendFilterAsync;
	
	uint32_t flags;
	uint16_t config;
	
	uint16_t max4ReceiveSize;
	uint32_t max6ReceiveSize;
	
	int socket4FD;
	int socket6FD;
	
	dispatch_queue_t socketQueue;
	
	dispatch_source_t send4Source;
	dispatch_source_t send6Source;
	dispatch_source_t receive4Source;
	dispatch_source_t receive6Source;
	dispatch_source_t sendTimer;
	
	GCDAsyncUdpSendPacket *currentSend;
	NSMutableArray *sendQueue;
	
	unsigned long socket4FDBytesAvailable;
	unsigned long socket6FDBytesAvailable;
	
	uint32_t pendingFilterOperations;
	
	NSData   *cachedLocalAddress4;
	NSString *cachedLocalHost4;
	uint16_t  cachedLocalPort4;
	
	NSData   *cachedLocalAddress6;
	NSString *cachedLocalHost6;
	uint16_t  cachedLocalPort6;
	
	NSData   *cachedConnectedAddress;
	NSString *cachedConnectedHost;
	uint16_t  cachedConnectedPort;
	int       cachedConnectedFamily;

	void *IsOnSocketQueueOrTargetQueueKey;    
	
#if TARGET_OS_IPHONE
	CFStreamClientContext streamContext;
	CFReadStreamRef readStream4;
	CFReadStreamRef readStream6;
	CFWriteStreamRef writeStream4;
	CFWriteStreamRef writeStream6;
#endif
	
	id userData;
}

- (void)resumeSend4Source;
- (void)resumeSend6Source;
- (void)resumeReceive4Source;
- (void)resumeReceive6Source;
- (void)closeSockets;

- (void)maybeConnect;
- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr;
- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr;

- (void)maybeDequeueSend;
- (void)doPreSend;
- (void)doSend;
- (void)endCurrentSend;
- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout;

- (void)doReceive;
- (void)doReceiveEOF;

- (void)closeWithError:(NSError *)error;

- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;

#if TARGET_OS_IPHONE
- (BOOL)createReadAndWriteStreams:(NSError **)errPtr;
- (BOOL)registerForStreamCallbacks:(NSError **)errPtr;
- (BOOL)addStreamsToRunLoop:(NSError **)errPtr;
- (BOOL)openStreams:(NSError **)errPtr;
- (void)removeStreamsFromRunLoop;
- (void)closeReadAndWriteStreams;
#endif

+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;

#if TARGET_OS_IPHONE
// Forward declaration
+ (void)listenerThread;
#endif

@end

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

/**
 * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write.
**/
@interface GCDAsyncUdpSendPacket : NSObject {
@public
	NSData *buffer;
	NSTimeInterval timeout;
	long tag;
	
	BOOL resolveInProgress;
	BOOL filterInProgress;
	
	NSArray *resolvedAddresses;
	NSError *resolveError;
	
	NSData *address;
	int addressFamily;
}

- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;

@end

@implementation GCDAsyncUdpSendPacket

- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
	if ((self = [super init]))
	{
		buffer = d;
		timeout = t;
		tag = i;
		
		resolveInProgress = NO;
	}
	return self;
}


@end

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

@interface GCDAsyncUdpSpecialPacket : NSObject {
@public
//	uint8_t type;
	
	BOOL resolveInProgress;
	
	NSArray *addresses;
	NSError *error;
}

- (id)init;

@end

@implementation GCDAsyncUdpSpecialPacket

- (id)init
{
	self = [super init];
	return self;
}


@end

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

@implementation GCDAsyncUdpSocket

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

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

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
	LogTrace();
	
	return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}

- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
	LogTrace();
	
	if ((self = [super init]))
	{
		delegate = aDelegate;
		
		if (dq)
		{
			delegateQueue = dq;
			#if !OS_OBJECT_USE_OBJC
			dispatch_retain(delegateQueue);
			#endif
		}
		
		max4ReceiveSize = 9216;
		max6ReceiveSize = 9216;
		
		socket4FD = SOCKET_NULL;
		socket6FD = SOCKET_NULL;
		
		if (sq)
		{
			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
			         @"The given socketQueue parameter must not be a concurrent queue.");
			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
			         @"The given socketQueue parameter must not be a concurrent queue.");
			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
			         @"The given socketQueue parameter must not be a concurrent queue.");
			
			socketQueue = sq;
			#if !OS_OBJECT_USE_OBJC
			dispatch_retain(socketQueue);
			#endif
		}
		else
		{
			socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL);
		}

		// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
		// From the documentation:
		//
		// > Keys are only compared as pointers and are never dereferenced.
		// > Thus, you can use a pointer to a static variable for a specific subsystem or
		// > any other value that allows you to identify the value uniquely.
		//
		// We're just going to use the memory address of an ivar.
		// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
		//
		// However, it feels tedious (and less readable) to include the "&" all the time:
		// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
		//
		// So we're going to make it so it doesn't matter if we use the '&' or not,
		// by assigning the value of the ivar to the address of the ivar.
		// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;

		IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;

		void *nonNullUnusedPointer = (__bridge void *)self;
		dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
		
		currentSend = nil;
		sendQueue = [[NSMutableArray alloc] initWithCapacity:5];
		
		#if TARGET_OS_IPHONE
		[[NSNotificationCenter defaultCenter] addObserver:self
		                                         selector:@selector(applicationWillEnterForeground:)
		                                             name:UIApplicationWillEnterForegroundNotification
		                                           object:nil];
		#endif
	}
	return self;
}

- (void)dealloc
{
	LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
	
#if TARGET_OS_IPHONE
	[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		[self closeWithError:nil];
	}
	else
	{
		dispatch_sync(socketQueue, ^{
			[self closeWithError:nil];
		});
	}
	
	delegate = nil;
	#if !OS_OBJECT_USE_OBJC
	if (delegateQueue) dispatch_release(delegateQueue);
	#endif
	delegateQueue = NULL;
	
	#if !OS_OBJECT_USE_OBJC
	if (socketQueue) dispatch_release(socketQueue);
	#endif
	socketQueue = NULL;
	
	LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
}

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

- (id)delegate
{
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		return delegate;
	}
	else
	{
		__block id result = nil;
		
		dispatch_sync(socketQueue, ^{
			result = delegate;
		});
		
		return result;
	}
}

- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
{
	dispatch_block_t block = ^{
		delegate = newDelegate;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
		block();
	}
	else {
		if (synchronously)
			dispatch_sync(socketQueue, block);
		else
			dispatch_async(socketQueue, block);
	}
}

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

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

- (dispatch_queue_t)delegateQueue
{
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		return delegateQueue;
	}
	else
	{
		__block dispatch_queue_t result = NULL;
		
		dispatch_sync(socketQueue, ^{
			result = delegateQueue;
		});
		
		return result;
	}
}

- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
	dispatch_block_t block = ^{
		
		#if !OS_OBJECT_USE_OBJC
		if (delegateQueue) dispatch_release(delegateQueue);
		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
		#endif
		
		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 *)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 = delegate;
			dqPtr = 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 = ^{
		
		delegate = newDelegate;
		
		#if !OS_OBJECT_USE_OBJC
		if (delegateQueue) dispatch_release(delegateQueue);
		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
		#endif
		
		delegateQueue = newDelegateQueue;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
		block();
	}
	else {
		if (synchronously)
			dispatch_sync(socketQueue, block);
		else
			dispatch_async(socketQueue, block);
	}
}

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

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

- (BOOL)isIPv4Enabled
{
	// Note: YES means kIPv4Disabled is OFF
	
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		
		result = ((config & kIPv4Disabled) == 0);
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setIPv4Enabled:(BOOL)flag
{
	// Note: YES means kIPv4Disabled is OFF
	
	dispatch_block_t block = ^{
		
		LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
		
		if (flag)
			config &= ~kIPv4Disabled;
		else
			config |= kIPv4Disabled;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (BOOL)isIPv6Enabled
{
	// Note: YES means kIPv6Disabled is OFF
	
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		
		result = ((config & kIPv6Disabled) == 0);
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setIPv6Enabled:(BOOL)flag
{
	// Note: YES means kIPv6Disabled is OFF
	
	dispatch_block_t block = ^{
		
		LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
		
		if (flag)
			config &= ~kIPv6Disabled;
		else
			config |= kIPv6Disabled;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (BOOL)isIPv4Preferred
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = (config & kPreferIPv4) ? YES : NO;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (BOOL)isIPv6Preferred
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = (config & kPreferIPv6) ? YES : NO;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (BOOL)isIPVersionNeutral
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = (config & (kPreferIPv4 | kPreferIPv6)) == 0;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setPreferIPv4
{
	dispatch_block_t block = ^{
		
		LogTrace();
		
		config |=  kPreferIPv4;
		config &= ~kPreferIPv6;
		
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (void)setPreferIPv6
{
	dispatch_block_t block = ^{
		
		LogTrace();
		
		config &= ~kPreferIPv4;
		config |=  kPreferIPv6;
		
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (void)setIPVersionNeutral
{
	dispatch_block_t block = ^{
		
		LogTrace();
		
		config &= ~kPreferIPv4;
		config &= ~kPreferIPv6;
		
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (uint16_t)maxReceiveIPv4BufferSize
{
	__block uint16_t result = 0;
	
	dispatch_block_t block = ^{
		
		result = max4ReceiveSize;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max
{
	dispatch_block_t block = ^{
		
		LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
		
		max4ReceiveSize = max;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (uint32_t)maxReceiveIPv6BufferSize
{
	__block uint32_t result = 0;
	
	dispatch_block_t block = ^{
		
		result = max6ReceiveSize;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max
{
	dispatch_block_t block = ^{
		
		LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
		
		max6ReceiveSize = max;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}


- (id)userData
{
	__block id result = nil;
	
	dispatch_block_t block = ^{
		
		result = userData;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (void)setUserData:(id)arbitraryUserData
{
	dispatch_block_t block = ^{
		
		if (userData != arbitraryUserData)
		{
			userData = arbitraryUserData;
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Delegate Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)notifyDidConnectToAddress:(NSData *)anAddress
{
	LogTrace();
	
	if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
	{
		id theDelegate = delegate;
		NSData *address = [anAddress copy]; // In case param is NSMutableData
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocket:self didConnectToAddress:address];
		}});
	}
}

- (void)notifyDidNotConnect:(NSError *)error
{
	LogTrace();
	
	if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
	{
		id theDelegate = delegate;
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocket:self didNotConnect:error];
		}});
	}
}

- (void)notifyDidSendDataWithTag:(long)tag
{
	LogTrace();
	
	if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
	{
		id theDelegate = delegate;
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocket:self didSendDataWithTag:tag];
		}});
	}
}

- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
{
	LogTrace();
	
	if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
	{
		id theDelegate = delegate;
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error];
		}});
	}
}

- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context
{
	LogTrace();
	
	SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:);
	
	if (delegateQueue && [delegate respondsToSelector:selector])
	{
		id theDelegate = delegate;
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
		}});
	}
}

- (void)notifyDidCloseWithError:(NSError *)error
{
	LogTrace();
	
	if (delegateQueue && [delegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
	{
		id theDelegate = delegate;
		
		dispatch_async(delegateQueue, ^{ @autoreleasepool {
			
			[theDelegate udpSocketDidClose:self withError:error];
		}});
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Errors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (NSError *)badConfigError:(NSString *)errMsg
{
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
	                           code:GCDAsyncUdpSocketBadConfigError
	                       userInfo:userInfo];
}

- (NSError *)badParamError:(NSString *)errMsg
{
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
	                           code:GCDAsyncUdpSocketBadParamError
	                       userInfo:userInfo];
}

- (NSError *)gaiError:(int)gai_error
{
	NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
}

- (NSError *)errnoErrorWithReason:(NSString *)reason
{
	NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
	NSDictionary *userInfo;
	
	if (reason)
		userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
		                                                      reason, NSLocalizedFailureReasonErrorKey, nil];
	else
		userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, nil];
	
	return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}

- (NSError *)errnoError
{
	return [self errnoErrorWithReason:nil];
}

/**
 * Returns a standard send timeout error.
**/
- (NSError *)sendTimeoutError
{
	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError",
	                                                     @"GCDAsyncUdpSocket", [NSBundle mainBundle],
	                                                     @"Send operation timed out", nil);
	
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
	                           code:GCDAsyncUdpSocketSendTimeoutError
	                       userInfo:userInfo];
}

- (NSError *)socketClosedError
{
	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
	                                                     @"GCDAsyncUdpSocket", [NSBundle mainBundle],
	                                                     @"Socket closed", nil);
	
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
}

- (NSError *)otherError:(NSString *)errMsg
{
	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
	
	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
	                           code:GCDAsyncUdpSocketOtherError
	                       userInfo:userInfo];
}

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

- (BOOL)preOp:(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 use socket without a delegate. Set a delegate first.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	if (delegateQueue == NULL) // Must have delegate queue set
	{
		if (errPtr)
		{
			NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	return YES;
}

/**
 * This method executes on a global concurrent queue.
 * When complete, it executes the given completion block on the socketQueue.
**/
- (void)asyncResolveHost:(NSString *)aHost
                    port:(uint16_t)port
     withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock
{
	LogTrace();
	
	// Check parameter(s)
	
	if (aHost == nil)
	{
		NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
		NSError *error = [self badParamError:msg];
		
		// We should still use dispatch_async since this method is expected to be asynchronous
		
		dispatch_async(socketQueue, ^{ @autoreleasepool {
			
			completionBlock(nil, error);
		}});
		
		return;
	}
	
	// It's possible that the given aHost parameter is actually a NSMutableString.
	// So we want to copy it now, within this block that will be executed synchronously.
	// This way the asynchronous lookup block below doesn't have to worry about it changing.
	
	NSString *host = [aHost copy];
	
	
	dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
	dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
		
		NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2];
		NSError *error = nil;
		
		if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
		{
			// Use LOOPBACK address
			struct sockaddr_in sockaddr4;
			memset(&sockaddr4, 0, sizeof(sockaddr4));
			
			sockaddr4.sin_len         = sizeof(struct sockaddr_in);
			sockaddr4.sin_family      = AF_INET;
			sockaddr4.sin_port        = htons(port);
			sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			
			struct sockaddr_in6 sockaddr6;
			memset(&sockaddr6, 0, sizeof(sockaddr6));
			
			sockaddr6.sin6_len       = sizeof(struct sockaddr_in6);
			sockaddr6.sin6_family    = AF_INET6;
			sockaddr6.sin6_port      = htons(port);
			sockaddr6.sin6_addr      = in6addr_loopback;
			
			// Wrap the native address structures and add to list
			[addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]];
			[addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]];
		}
		else
		{
			NSString *portStr = [NSString stringWithFormat:@"%hu", port];
			
			struct addrinfo hints, *res, *res0;
			
			memset(&hints, 0, sizeof(hints));
			hints.ai_family   = PF_UNSPEC;
			hints.ai_socktype = SOCK_DGRAM;
			hints.ai_protocol = IPPROTO_UDP;
			
			int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
			
			if (gai_error)
			{
				error = [self gaiError:gai_error];
			}
			else
			{
				for(res = res0; res; res = res->ai_next)
				{
					if (res->ai_family == AF_INET)
					{
						// Found IPv4 address
						// Wrap the native address structure and add to list
						
						[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
					}
					else if (res->ai_family == AF_INET6)
					{
						// Found IPv6 address
						// Wrap the native address structure and add to list
						
						[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
					}
				}
				freeaddrinfo(res0);
				
				if ([addresses count] == 0)
				{
					error = [self gaiError:EAI_FAIL];
				}
			}
		}
		
		dispatch_async(socketQueue, ^{ @autoreleasepool {
			
			completionBlock(addresses, error);
		}});
		
	}});
}

/**
 * This method picks an address from the given list of addresses.
 * The address picked depends upon which protocols are disabled, deactived, & preferred.
 * 
 * Returns the address family (AF_INET or AF_INET6) of the picked address,
 * or AF_UNSPEC and the corresponding error is there's a problem.
**/
- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert([addresses count] > 0, @"Expected at least one address");
	
	int resultAF = AF_UNSPEC;
	NSData *resultAddress = nil;
	NSError *resultError = nil;
	
	// Check for problems
	
	BOOL resolvedIPv4Address = NO;
	BOOL resolvedIPv6Address = NO;
	
	for (NSData *address in addresses)
	{
		switch ([[self class] familyFromAddress:address])
		{
			case AF_INET  : resolvedIPv4Address = YES; break;
			case AF_INET6 : resolvedIPv6Address = YES; break;
			
			default       : NSAssert(NO, @"Addresses array contains invalid address");
		}
	}
	
	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
	
	if (isIPv4Disabled && !resolvedIPv6Address)
	{
		NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es).";
		resultError = [self otherError:msg];
		
		if (addressPtr) *addressPtr = resultAddress;
		if (errorPtr) *errorPtr = resultError;
		
		return resultAF;
	}
	
	if (isIPv6Disabled && !resolvedIPv4Address)
	{
		NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es).";
		resultError = [self otherError:msg];
		
		if (addressPtr) *addressPtr = resultAddress;
		if (errorPtr) *errorPtr = resultError;
		
		return resultAF;
	}
	
	BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO;
	BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO;
	
	if (isIPv4Deactivated && !resolvedIPv6Address)
	{
		NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es).";
		resultError = [self otherError:msg];
		
		if (addressPtr) *addressPtr = resultAddress;
		if (errorPtr) *errorPtr = resultError;
		
		return resultAF;
	}
	
	if (isIPv6Deactivated && !resolvedIPv4Address)
	{
		NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es).";
		resultError = [self otherError:msg];
		
		if (addressPtr) *addressPtr = resultAddress;
		if (errorPtr) *errorPtr = resultError;
		
		return resultAF;
	}
	
	// Extract first IPv4 and IPv6 address in list
	
	BOOL ipv4WasFirstInList = YES;
	NSData *address4 = nil;
	NSData *address6 = nil;
	
	for (NSData *address in addresses)
	{
		int af = [[self class] familyFromAddress:address];
		
		if (af == AF_INET)
		{
			if (address4 == nil)
			{
				address4 = address;
				
				if (address6)
					break;
				else
					ipv4WasFirstInList = YES;
			}
		}
		else // af == AF_INET6
		{
			if (address6 == nil)
			{
				address6 = address;
				
				if (address4)
					break;
				else
					ipv4WasFirstInList = NO;
			}
		}
	}
	
	// Determine socket type
	
	BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO;
	BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
	
	BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil));
	BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
	
	NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state");
	NSAssert(!(useIPv4 && useIPv6), @"Invalid logic");
	
	if (useIPv4 || (!useIPv6 && ipv4WasFirstInList))
	{
		resultAF = AF_INET;
		resultAddress = address4;
	}
	else
	{
		resultAF = AF_INET6;
		resultAddress = address6;
	}
	
	if (addressPtr) *addressPtr = resultAddress;
	if (errorPtr) *errorPtr = resultError;
		
	return resultAF;
}

/**
 * Finds the address(es) of an interface description.
 * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
**/
- (void)convertIntefaceDescription:(NSString *)interfaceDescription
                              port:(uint16_t)port
                      intoAddress4:(NSData **)interfaceAddr4Ptr
                          address6:(NSData **)interfaceAddr6Ptr
{
	NSData *addr4 = nil;
	NSData *addr6 = nil;
	
	if (interfaceDescription == nil)
	{
		// ANY address
		
		struct sockaddr_in sockaddr4;
		memset(&sockaddr4, 0, sizeof(sockaddr4));
		
		sockaddr4.sin_len         = sizeof(sockaddr4);
		sockaddr4.sin_family      = AF_INET;
		sockaddr4.sin_port        = htons(port);
		sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
		
		struct sockaddr_in6 sockaddr6;
		memset(&sockaddr6, 0, sizeof(sockaddr6));
		
		sockaddr6.sin6_len       = sizeof(sockaddr6);
		sockaddr6.sin6_family    = AF_INET6;
		sockaddr6.sin6_port      = htons(port);
		sockaddr6.sin6_addr      = in6addr_any;
		
		addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
		addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
	}
	else if ([interfaceDescription isEqualToString:@"localhost"] ||
	         [interfaceDescription isEqualToString:@"loopback"])
	{
		// LOOPBACK address
		
		struct sockaddr_in sockaddr4;
		memset(&sockaddr4, 0, sizeof(sockaddr4));
		
		sockaddr4.sin_len         = sizeof(struct sockaddr_in);
		sockaddr4.sin_family      = AF_INET;
		sockaddr4.sin_port        = htons(port);
		sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		
		struct sockaddr_in6 sockaddr6;
		memset(&sockaddr6, 0, sizeof(sockaddr6));
		
		sockaddr6.sin6_len       = sizeof(struct sockaddr_in6);
		sockaddr6.sin6_family    = AF_INET6;
		sockaddr6.sin6_port      = htons(port);
		sockaddr6.sin6_addr      = in6addr_loopback;
		
		addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
		addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
	}
	else
	{
		const char *iface = [interfaceDescription UTF8String];
		
		struct ifaddrs *addrs;
		const struct ifaddrs *cursor;
		
		if ((getifaddrs(&addrs) == 0))
		{
			cursor = addrs;
			while (cursor != NULL)
			{
				if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
				{
					// IPv4
					
					struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr;
					
					if (strcmp(cursor->ifa_name, iface) == 0)
					{
						// Name match
						
						struct sockaddr_in nativeAddr4 = *addr;
						nativeAddr4.sin_port = htons(port);
						
						addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
					}
					else
					{
						char ip[INET_ADDRSTRLEN];
						
						const char *conversion;
						conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
						
						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
						{
							// IP match
							
							struct sockaddr_in nativeAddr4 = *addr;
							nativeAddr4.sin_port = htons(port);
							
							addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
						}
					}
				}
				else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
				{
					// IPv6
					
					struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr;
					
					if (strcmp(cursor->ifa_name, iface) == 0)
					{
						// Name match
						
						struct sockaddr_in6 nativeAddr6 = *addr;
						nativeAddr6.sin6_port = htons(port);
						
						addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
					}
					else
					{
						char ip[INET6_ADDRSTRLEN];
						
						const char *conversion;
						conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
						
						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
						{
							// IP match
							
							struct sockaddr_in6 nativeAddr6 = *addr;
							nativeAddr6.sin6_port = htons(port);
							
							addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
						}
					}
				}
				
				cursor = cursor->ifa_next;
			}
			
			freeifaddrs(addrs);
		}
	}
	
	if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
	if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}

/**
 * Converts a numeric hostname into its corresponding address.
 * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34)
**/
- (void)convertNumericHost:(NSString *)numericHost
                      port:(uint16_t)port
              intoAddress4:(NSData **)addr4Ptr
                  address6:(NSData **)addr6Ptr
{
	NSData *addr4 = nil;
	NSData *addr6 = nil;
	
	if (numericHost)
	{
		NSString *portStr = [NSString stringWithFormat:@"%hu", port];
		
		struct addrinfo hints, *res, *res0;
		
		memset(&hints, 0, sizeof(hints));
		hints.ai_family   = PF_UNSPEC;
		hints.ai_socktype = SOCK_DGRAM;
		hints.ai_protocol = IPPROTO_UDP;
		hints.ai_flags    = AI_NUMERICHOST; // No name resolution should be attempted
		
		if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0)
		{
			for (res = res0; res; res = res->ai_next)
			{
				if ((addr4 == nil) && (res->ai_family == AF_INET))
				{
					// Found IPv4 address
					// Wrap the native address structure
					addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
				}
				else if ((addr6 == nil) && (res->ai_family == AF_INET6))
				{
					// Found IPv6 address
					// Wrap the native address structure
					addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
				}
			}
			freeaddrinfo(res0);
		}
	}
	
	if (addr4Ptr) *addr4Ptr = addr4;
	if (addr6Ptr) *addr6Ptr = addr6;
}

- (BOOL)isConnectedToAddress4:(NSData *)someAddr4
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(flags & kDidConnect, @"Not connected");
	NSAssert(cachedConnectedAddress, @"Expected cached connected address");
	
	if (cachedConnectedFamily != AF_INET)
	{
		return NO;
	}
	
	const struct sockaddr_in *sSockaddr4 = (struct sockaddr_in *)[someAddr4 bytes];
	const struct sockaddr_in *cSockaddr4 = (struct sockaddr_in *)[cachedConnectedAddress bytes];
	
	if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0)
	{
		return NO;
	}
	if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0)
	{
		return NO;
	}
	
	return YES;
}

- (BOOL)isConnectedToAddress6:(NSData *)someAddr6
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(flags & kDidConnect, @"Not connected");
	NSAssert(cachedConnectedAddress, @"Expected cached connected address");
	
	if (cachedConnectedFamily != AF_INET6)
	{
		return NO;
	}
	
	const struct sockaddr_in6 *sSockaddr6 = (struct sockaddr_in6 *)[someAddr6 bytes];
	const struct sockaddr_in6 *cSockaddr6 = (struct sockaddr_in6 *)[cachedConnectedAddress bytes];
	
	if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0)
	{
		return NO;
	}
	if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0)
	{
		return NO;
	}
	
	return YES;
}

- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4
{
	if (interfaceAddr4 == nil)
		return 0;
	if ([interfaceAddr4 length] != sizeof(struct sockaddr_in))
		return 0;
	
	int result = 0;
	struct sockaddr_in *ifaceAddr = (struct sockaddr_in *)[interfaceAddr4 bytes];
	
	struct ifaddrs *addrs;
	const struct ifaddrs *cursor;
	
	if ((getifaddrs(&addrs) == 0))
	{
		cursor = addrs;
		while (cursor != NULL)
		{
			if (cursor->ifa_addr->sa_family == AF_INET)
			{
				// IPv4
				
				struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr;
				
				if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0)
				{
					result = if_nametoindex(cursor->ifa_name);
					break;
				}
			}
			
			cursor = cursor->ifa_next;
		}
		
		freeifaddrs(addrs);
	}
	
	return result;
}

- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6
{
	if (interfaceAddr6 == nil)
		return 0;
	if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6))
		return 0;
	
	int result = 0;
	struct sockaddr_in6 *ifaceAddr = (struct sockaddr_in6 *)[interfaceAddr6 bytes];
	
	struct ifaddrs *addrs;
	const struct ifaddrs *cursor;
	
	if ((getifaddrs(&addrs) == 0))
	{
		cursor = addrs;
		while (cursor != NULL)
		{
			if (cursor->ifa_addr->sa_family == AF_INET6)
			{
				// IPv6
				
				struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr;
				
				if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0)
				{
					result = if_nametoindex(cursor->ifa_name);
					break;
				}
			}
			
			cursor = cursor->ifa_next;
		}
		
		freeifaddrs(addrs);
	}
	
	return result;
}

- (void)setupSendAndReceiveSourcesForSocket4
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue);
	receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
	
	// Setup event handlers
	
	dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool {
		
		LogVerbose(@"send4EventBlock");
		LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source));
		
		flags |= kSock4CanAcceptBytes;
		
		// If we're ready to send data, do so immediately.
		// Otherwise pause the send source or it will continue to fire over and over again.
		
		if (currentSend == nil)
		{
			LogVerbose(@"Nothing to send");
			[self suspendSend4Source];
		}
		else if (currentSend->resolveInProgress)
		{
			LogVerbose(@"currentSend - waiting for address resolve");
			[self suspendSend4Source];
		}
		else if (currentSend->filterInProgress)
		{
			LogVerbose(@"currentSend - waiting on sendFilter");
			[self suspendSend4Source];
		}
		else
		{
			[self doSend];
		}
		
	}});
	
	dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
		
		LogVerbose(@"receive4EventBlock");
		
		socket4FDBytesAvailable = dispatch_source_get_data(receive4Source);
		LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
		
		if (socket4FDBytesAvailable > 0)
			[self doReceive];
		else
			[self doReceiveEOF];
		
	}});
	
	// Setup cancel handlers
	
	__block int socketFDRefCount = 2;
	
	int theSocketFD = socket4FD;
	
	#if !OS_OBJECT_USE_OBJC
	dispatch_source_t theSendSource = send4Source;
	dispatch_source_t theReceiveSource = receive4Source;
	#endif
	
	dispatch_source_set_cancel_handler(send4Source, ^{
		
		LogVerbose(@"send4CancelBlock");
		
		#if !OS_OBJECT_USE_OBJC
		LogVerbose(@"dispatch_release(send4Source)");
		dispatch_release(theSendSource);
		#endif
		
		if (--socketFDRefCount == 0)
		{
			LogVerbose(@"close(socket4FD)");
			close(theSocketFD);
		}
	});
	
	dispatch_source_set_cancel_handler(receive4Source, ^{
		
		LogVerbose(@"receive4CancelBlock");
		
		#if !OS_OBJECT_USE_OBJC
		LogVerbose(@"dispatch_release(receive4Source)");
		dispatch_release(theReceiveSource);
		#endif
		
		if (--socketFDRefCount == 0)
		{
			LogVerbose(@"close(socket4FD)");
			close(theSocketFD);
		}
	});
	
	// We will not be able to receive until the socket is bound to a port,
	// either explicitly via bind, or implicitly by connect or by sending data.
	// 
	// But we should be able to send immediately.
	
	socket4FDBytesAvailable = 0;
	flags |= kSock4CanAcceptBytes;
	
	flags |= kSend4SourceSuspended;
	flags |= kReceive4SourceSuspended;
}

- (void)setupSendAndReceiveSourcesForSocket6
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue);
	receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
	
	// Setup event handlers
	
	dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool {
		
		LogVerbose(@"send6EventBlock");
		LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source));
		
		flags |= kSock6CanAcceptBytes;
		
		// If we're ready to send data, do so immediately.
		// Otherwise pause the send source or it will continue to fire over and over again.
		
		if (currentSend == nil)
		{
			LogVerbose(@"Nothing to send");
			[self suspendSend6Source];
		}
		else if (currentSend->resolveInProgress)
		{
			LogVerbose(@"currentSend - waiting for address resolve");
			[self suspendSend6Source];
		}
		else if (currentSend->filterInProgress)
		{
			LogVerbose(@"currentSend - waiting on sendFilter");
			[self suspendSend6Source];
		}
		else
		{
			[self doSend];
		}
		
	}});
	
	dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
		
		LogVerbose(@"receive6EventBlock");
		
		socket6FDBytesAvailable = dispatch_source_get_data(receive6Source);
		LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
		
		if (socket6FDBytesAvailable > 0)
			[self doReceive];
		else
			[self doReceiveEOF];
		
	}});
	
	// Setup cancel handlers
	
	__block int socketFDRefCount = 2;
	
	int theSocketFD = socket6FD;
	
	#if !OS_OBJECT_USE_OBJC
	dispatch_source_t theSendSource = send6Source;
	dispatch_source_t theReceiveSource = receive6Source;
	#endif
	
	dispatch_source_set_cancel_handler(send6Source, ^{
		
		LogVerbose(@"send6CancelBlock");
		
		#if !OS_OBJECT_USE_OBJC
		LogVerbose(@"dispatch_release(send6Source)");
		dispatch_release(theSendSource);
		#endif
		
		if (--socketFDRefCount == 0)
		{
			LogVerbose(@"close(socket6FD)");
			close(theSocketFD);
		}
	});
	
	dispatch_source_set_cancel_handler(receive6Source, ^{
		
		LogVerbose(@"receive6CancelBlock");
		
		#if !OS_OBJECT_USE_OBJC
		LogVerbose(@"dispatch_release(receive6Source)");
		dispatch_release(theReceiveSource);
		#endif
		
		if (--socketFDRefCount == 0)
		{
			LogVerbose(@"close(socket6FD)");
			close(theSocketFD);
		}
	});
	
	// We will not be able to receive until the socket is bound to a port,
	// either explicitly via bind, or implicitly by connect or by sending data.
	// 
	// But we should be able to send immediately.
	
	socket6FDBytesAvailable = 0;
	flags |= kSock6CanAcceptBytes;
	
	flags |= kSend6SourceSuspended;
	flags |= kReceive6SourceSuspended;
}

- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError **)errPtr
{
	LogTrace();
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created");
	
	// CreateSocket Block
	// This block will be invoked below.
	
	int(^createSocket)(int) = ^int (int domain) {
		
		int socketFD = socket(domain, SOCK_DGRAM, 0);
		
		if (socketFD == SOCKET_NULL)
		{
			if (errPtr)
				*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
			
			return SOCKET_NULL;
		}
		
		int status;
		
		// Set socket options
		
		status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
		if (status == -1)
		{
			if (errPtr)
				*errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"];
			
			close(socketFD);
			return SOCKET_NULL;
		}
		
		int reuseaddr = 1;
		status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
		if (status == -1)
		{
			if (errPtr)
				*errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"];
			
			close(socketFD);
			return SOCKET_NULL;
		}
		
		int nosigpipe = 1;
		status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
		if (status == -1)
		{
			if (errPtr)
				*errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"];
			
			close(socketFD);
			return SOCKET_NULL;
		}
		
		return socketFD;
	};
	
	// Create sockets depending upon given configuration.
	
	if (useIPv4)
	{
		LogVerbose(@"Creating IPv4 socket");
		
		socket4FD = createSocket(AF_INET);
		if (socket4FD == SOCKET_NULL)
		{
			// errPtr set in local createSocket() block
			return NO;
		}
	}
	
	if (useIPv6)
	{
		LogVerbose(@"Creating IPv6 socket");
		
		socket6FD = createSocket(AF_INET6);
		if (socket6FD == SOCKET_NULL)
		{
			// errPtr set in local createSocket() block
			
			if (socket4FD != SOCKET_NULL)
			{
				close(socket4FD);
				socket4FD = SOCKET_NULL;
			}
			
			return NO;
		}
	}
	
	// Setup send and receive sources
	
	if (useIPv4)
		[self setupSendAndReceiveSourcesForSocket4];
	if (useIPv6)
		[self setupSendAndReceiveSourcesForSocket6];
	
	flags |= kDidCreateSockets;
	return YES;
}

- (BOOL)createSockets:(NSError **)errPtr
{
	LogTrace();
	
	BOOL useIPv4 = [self isIPv4Enabled];
	BOOL useIPv6 = [self isIPv6Enabled];
	
	return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr];
}

- (void)suspendSend4Source
{
	if (send4Source && !(flags & kSend4SourceSuspended))
	{
		LogVerbose(@"dispatch_suspend(send4Source)");
		
		dispatch_suspend(send4Source);
		flags |= kSend4SourceSuspended;
	}
}

- (void)suspendSend6Source
{
	if (send6Source && !(flags & kSend6SourceSuspended))
	{
		LogVerbose(@"dispatch_suspend(send6Source)");
		
		dispatch_suspend(send6Source);
		flags |= kSend6SourceSuspended;
	}
}

- (void)resumeSend4Source
{
	if (send4Source && (flags & kSend4SourceSuspended))
	{
		LogVerbose(@"dispatch_resume(send4Source)");
		
		dispatch_resume(send4Source);
		flags &= ~kSend4SourceSuspended;
	}
}

- (void)resumeSend6Source
{
	if (send6Source && (flags & kSend6SourceSuspended))
	{
		LogVerbose(@"dispatch_resume(send6Source)");
		
		dispatch_resume(send6Source);
		flags &= ~kSend6SourceSuspended;
	}
}

- (void)suspendReceive4Source
{
	if (receive4Source && !(flags & kReceive4SourceSuspended))
	{
		LogVerbose(@"dispatch_suspend(receive4Source)");
		
		dispatch_suspend(receive4Source);
		flags |= kReceive4SourceSuspended;
	}
}

- (void)suspendReceive6Source
{
	if (receive6Source && !(flags & kReceive6SourceSuspended))
	{
		LogVerbose(@"dispatch_suspend(receive6Source)");
		
		dispatch_suspend(receive6Source);
		flags |= kReceive6SourceSuspended;
	}
}

- (void)resumeReceive4Source
{
	if (receive4Source && (flags & kReceive4SourceSuspended))
	{
		LogVerbose(@"dispatch_resume(receive4Source)");
		
		dispatch_resume(receive4Source);
		flags &= ~kReceive4SourceSuspended;
	}
}

- (void)resumeReceive6Source
{
	if (receive6Source && (flags & kReceive6SourceSuspended))
	{
		LogVerbose(@"dispatch_resume(receive6Source)");
		
		dispatch_resume(receive6Source);
		flags &= ~kReceive6SourceSuspended;
	}
}

- (void)closeSocket4
{
	if (socket4FD != SOCKET_NULL)
	{
		LogVerbose(@"dispatch_source_cancel(send4Source)");
		dispatch_source_cancel(send4Source);
		
		LogVerbose(@"dispatch_source_cancel(receive4Source)");
		dispatch_source_cancel(receive4Source);
		
		// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
		// invoke the cancel handler if the dispatch source is paused.
		// So we have to unpause the source if needed.
		// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
		
		[self resumeSend4Source];
		[self resumeReceive4Source];
		
		// The sockets will be closed by the cancel handlers of the corresponding source
		
		send4Source = NULL;
		receive4Source = NULL;
		
		socket4FD = SOCKET_NULL;
		
		// Clear socket states
		
		socket4FDBytesAvailable = 0;
		flags &= ~kSock4CanAcceptBytes;
		
		// Clear cached info
		
		cachedLocalAddress4 = nil;
		cachedLocalHost4 = nil;
		cachedLocalPort4 = 0;
	}
}

- (void)closeSocket6
{
	if (socket6FD != SOCKET_NULL)
	{
		LogVerbose(@"dispatch_source_cancel(send6Source)");
		dispatch_source_cancel(send6Source);
		
		LogVerbose(@"dispatch_source_cancel(receive6Source)");
		dispatch_source_cancel(receive6Source);
		
		// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
		// invoke the cancel handler if the dispatch source is paused.
		// So we have to unpause the source if needed.
		// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
		
		[self resumeSend6Source];
		[self resumeReceive6Source];
		
		send6Source = NULL;
		receive6Source = NULL;
		
		// The sockets will be closed by the cancel handlers of the corresponding source
		
		socket6FD = SOCKET_NULL;
		
		// Clear socket states
		
		socket6FDBytesAvailable = 0;
		flags &= ~kSock6CanAcceptBytes;
		
		// Clear cached info
		
		cachedLocalAddress6 = nil;
		cachedLocalHost6 = nil;
		cachedLocalPort6 = 0;
	}
}

- (void)closeSockets
{
	[self closeSocket4];
	[self closeSocket6];
	
	flags &= ~kDidCreateSockets;
}

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

- (BOOL)getLocalAddress:(NSData **)dataPtr
                   host:(NSString **)hostPtr
                   port:(uint16_t *)portPtr
              forSocket:(int)socketFD
             withFamily:(int)socketFamily
{
	
	NSData   *data = nil;
	NSString *host = nil;
	uint16_t  port = 0;
	
	if (socketFamily == AF_INET)
	{
		struct sockaddr_in sockaddr4;
		socklen_t sockaddr4len = sizeof(sockaddr4);
		
		if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
		{
			data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
			host = [[self class] hostFromSockaddr4:&sockaddr4];
			port = [[self class] portFromSockaddr4:&sockaddr4];
		}
		else
		{
			LogWarn(@"Error in getsockname: %@", [self errnoError]);
		}
	}
	else if (socketFamily == AF_INET6)
	{
		struct sockaddr_in6 sockaddr6;
		socklen_t sockaddr6len = sizeof(sockaddr6);
		
		if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
		{
			data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
			host = [[self class] hostFromSockaddr6:&sockaddr6];
			port = [[self class] portFromSockaddr6:&sockaddr6];
		}
		else
		{
			LogWarn(@"Error in getsockname: %@", [self errnoError]);
		}
	}
	
	if (dataPtr) *dataPtr = data;
	if (hostPtr) *hostPtr = host;
	if (portPtr) *portPtr = port;
	
	return (data != nil);
}

- (void)maybeUpdateCachedLocalAddress4Info
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) )
	{
		return;
	}
	
	NSData *address = nil;
	NSString *host = nil;
	uint16_t port = 0;
	
	if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET])
	{
		
		cachedLocalAddress4 = address;
		cachedLocalHost4 = host;
		cachedLocalPort4 = port;
	}
}

- (void)maybeUpdateCachedLocalAddress6Info
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) )
	{
		return;
	}
	
	NSData *address = nil;
	NSString *host = nil;
	uint16_t port = 0;
	
	if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6])
	{
		
		cachedLocalAddress6 = address;
		cachedLocalHost6 = host;
		cachedLocalPort6 = port;
	}
}

- (NSData *)localAddress
{
	__block NSData *result = nil;
	
	dispatch_block_t block = ^{
		
		if (socket4FD != SOCKET_NULL)
		{
			[self maybeUpdateCachedLocalAddress4Info];
			result = cachedLocalAddress4;
		}
		else
		{
			[self maybeUpdateCachedLocalAddress6Info];
			result = cachedLocalAddress6;
		}
		
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSString *)localHost
{
	__block NSString *result = nil;
	
	dispatch_block_t block = ^{
		
		if (socket4FD != SOCKET_NULL)
		{
			[self maybeUpdateCachedLocalAddress4Info];
			result = cachedLocalHost4;
		}
		else
		{
			[self maybeUpdateCachedLocalAddress6Info];
			result = cachedLocalHost6;
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (uint16_t)localPort
{
	__block uint16_t result = 0;
	
	dispatch_block_t block = ^{
		
		if (socket4FD != SOCKET_NULL)
		{
			[self maybeUpdateCachedLocalAddress4Info];
			result = cachedLocalPort4;
		}
		else
		{
			[self maybeUpdateCachedLocalAddress6Info];
			result = cachedLocalPort6;
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSData *)localAddress_IPv4
{
	__block NSData *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress4Info];
		result = cachedLocalAddress4;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSString *)localHost_IPv4
{
	__block NSString *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress4Info];
		result = cachedLocalHost4;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (uint16_t)localPort_IPv4
{
	__block uint16_t result = 0;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress4Info];
		result = cachedLocalPort4;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSData *)localAddress_IPv6
{
	__block NSData *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress6Info];
		result = cachedLocalAddress6;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSString *)localHost_IPv6
{
	__block NSString *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress6Info];
		result = cachedLocalHost6;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (uint16_t)localPort_IPv6
{
	__block uint16_t result = 0;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedLocalAddress6Info];
		result = cachedLocalPort6;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (void)maybeUpdateCachedConnectedAddressInfo
{
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	if (cachedConnectedAddress || (flags & kDidConnect) == 0)
	{
		return;
	}
	
	NSData *data = nil;
	NSString *host = nil;
	uint16_t port = 0;
	int family = AF_UNSPEC;
	
	if (socket4FD != SOCKET_NULL)
	{
		struct sockaddr_in sockaddr4;
		socklen_t sockaddr4len = sizeof(sockaddr4);
		
		if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
		{
			data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
			host = [[self class] hostFromSockaddr4:&sockaddr4];
			port = [[self class] portFromSockaddr4:&sockaddr4];
			family = AF_INET;
		}
		else
		{
			LogWarn(@"Error in getpeername: %@", [self errnoError]);
		}
	}
	else if (socket6FD != SOCKET_NULL)
	{
		struct sockaddr_in6 sockaddr6;
		socklen_t sockaddr6len = sizeof(sockaddr6);
		
		if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
		{
			data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
			host = [[self class] hostFromSockaddr6:&sockaddr6];
			port = [[self class] portFromSockaddr6:&sockaddr6];
			family = AF_INET6;
		}
		else
		{
			LogWarn(@"Error in getpeername: %@", [self errnoError]);
		}
	}
	
	
	cachedConnectedAddress = data;
	cachedConnectedHost    = host;
	cachedConnectedPort    = port;
	cachedConnectedFamily  = family;
}

- (NSData *)connectedAddress
{
	__block NSData *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedConnectedAddressInfo];
		result = cachedConnectedAddress;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (NSString *)connectedHost
{
	__block NSString *result = nil;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedConnectedAddressInfo];
		result = cachedConnectedHost;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (uint16_t)connectedPort
{
	__block uint16_t result = 0;
	
	dispatch_block_t block = ^{
		
		[self maybeUpdateCachedConnectedAddressInfo];
		result = cachedConnectedPort;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, AutoreleasedBlock(block));
	
	return result;
}

- (BOOL)isConnected
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		result = (flags & kDidConnect) ? YES : NO;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (BOOL)isClosed
{
	__block BOOL result = YES;
	
	dispatch_block_t block = ^{
		
		result = (flags & kDidCreateSockets) ? NO : YES;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (BOOL)isIPv4
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		
		if (flags & kDidCreateSockets)
		{
			result = (socket4FD != SOCKET_NULL);
		}
		else
		{
			result = [self isIPv4Enabled];
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

- (BOOL)isIPv6
{
	__block BOOL result = NO;
	
	dispatch_block_t block = ^{
		
		if (flags & kDidCreateSockets)
		{
			result = (socket6FD != SOCKET_NULL);
		}
		else
		{
			result = [self isIPv6Enabled];
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Binding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * This method runs through the various checks required prior to a bind attempt.
 * It is shared between the various bind methods.
**/
- (BOOL)preBind:(NSError **)errPtr
{
	if (![self preOp:errPtr])
	{
		return NO;
	}
	
	if (flags & kDidBind)
	{
		if (errPtr)
		{
			NSString *msg = @"Cannot bind a socket more than once.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	if ((flags & kConnecting) || (flags & kDidConnect))
	{
		if (errPtr)
		{
			NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
	
	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
	{
		if (errPtr)
		{
			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	return YES;
}

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

- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		// Run through sanity checks
		
		if (![self preBind:&err])
		{
			return_from_block;
		}
		
		// Check the given interface
		
		NSData *interface4 = nil;
		NSData *interface6 = nil;
		
		[self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6];
		
		if ((interface4 == nil) && (interface6 == nil))
		{
			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
		BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
		
		if (isIPv4Disabled && (interface6 == nil))
		{
			NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
			err = [self badParamError:msg];
			
			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;
		}
		
		// Determine protocol(s)
		
		BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil);
		BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil);
		
		// Create the socket(s) if needed
		
		if ((flags & kDidCreateSockets) == 0)
		{
			if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
			{
				return_from_block;
			}
		}
		
		// Bind the socket(s)
		
		LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface);
		
		if (useIPv4)
		{
			int status = bind(socket4FD, (struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]);
			if (status == -1)
			{
				[self closeSockets];
				
				NSString *reason = @"Error in bind() function";
				err = [self errnoErrorWithReason:reason];
				
				return_from_block;
			}
		}
		
		if (useIPv6)
		{
			int status = bind(socket6FD, (struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]);
			if (status == -1)
			{
				[self closeSockets];
				
				NSString *reason = @"Error in bind() function";
				err = [self errnoErrorWithReason:reason];
				
				return_from_block;
			}
		}
		
		// Update flags
		
		flags |= kDidBind;
		
		if (!useIPv4) flags |= kIPv4Deactivated;
		if (!useIPv6) flags |= kIPv6Deactivated;
		
		result = YES;
		
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error binding to port/interface: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		// Run through sanity checks
		
		if (![self preBind:&err])
		{
			return_from_block;
		}
		
		// Check the given address
		
		int addressFamily = [[self class] familyFromAddress:localAddr];
		
		if (addressFamily == AF_UNSPEC)
		{
			NSString *msg = @"A valid IPv4 or IPv6 address was not given";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		NSData *localAddr4 = (addressFamily == AF_INET)  ? localAddr : nil;
		NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil;
		
		BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
		BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
		
		if (isIPv4Disabled && localAddr4)
		{
			NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		if (isIPv6Disabled && localAddr6)
		{
			NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		// Determine protocol(s)
		
		BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil);
		BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil);
		
		// Create the socket(s) if needed
		
		if ((flags & kDidCreateSockets) == 0)
		{
			if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
			{
				return_from_block;
			}
		}
		
		// Bind the socket(s)
		
		if (useIPv4)
		{
			LogVerbose(@"Binding socket to address(%@:%hu)",
					   [[self class] hostFromAddress:localAddr4],
					   [[self class] portFromAddress:localAddr4]);
			
			int status = bind(socket4FD, (struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]);
			if (status == -1)
			{
				[self closeSockets];
				
				NSString *reason = @"Error in bind() function";
				err = [self errnoErrorWithReason:reason];
				
				return_from_block;
			}
		}
		else
		{
			LogVerbose(@"Binding socket to address(%@:%hu)",
					   [[self class] hostFromAddress:localAddr6],
					   [[self class] portFromAddress:localAddr6]);
			
			int status = bind(socket6FD, (struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]);
			if (status == -1)
			{
				[self closeSockets];
				
				NSString *reason = @"Error in bind() function";
				err = [self errnoErrorWithReason:reason];
				
				return_from_block;
			}
		}
		
		// Update flags
		
		flags |= kDidBind;
		
		if (!useIPv4) flags |= kIPv4Deactivated;
		if (!useIPv6) flags |= kIPv6Deactivated;
		
		result = YES;
		
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error binding to address: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

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

/**
 * This method runs through the various checks required prior to a connect attempt.
 * It is shared between the various connect methods.
**/
- (BOOL)preConnect:(NSError **)errPtr
{
	if (![self preOp:errPtr])
	{
		return NO;
	}
	
	if ((flags & kConnecting) || (flags & kDidConnect))
	{
		if (errPtr)
		{
			NSString *msg = @"Cannot connect a socket more than once.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
	
	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
	{
		if (errPtr)
		{
			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	return YES;
}

- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		// Run through sanity checks.
		
		if (![self preConnect:&err])
		{
			return_from_block;
		}
		
		// Check parameter(s)
		
		if (host == nil)
		{
			NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		// Create the socket(s) if needed
		
		if ((flags & kDidCreateSockets) == 0)
		{
			if (![self createSockets:&err])
			{
				return_from_block;
			}
		}
		
		// Create special connect packet
		
		GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
		packet->resolveInProgress = YES;
		
		// Start asynchronous DNS resolve for host:port on background queue
		
		LogVerbose(@"Dispatching DNS resolve for connect...");
		
		[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
			
			// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
			// and immediately returns. Once the async resolve task completes,
			// this block is executed on our socketQueue.
			
			packet->resolveInProgress = NO;
			
			packet->addresses = addresses;
			packet->error = error;
			
			[self maybeConnect];
		}];
		
		// Updates flags, add connect packet to send queue, and pump send queue
		
		flags |= kConnecting;
		
		[sendQueue addObject:packet];
		[self maybeDequeueSend];
		
		result = YES;
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error connecting to host/port: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		// Run through sanity checks.
		
		if (![self preConnect:&err])
		{
			return_from_block;
		}
		
		// Check parameter(s)
		
		if (remoteAddr == nil)
		{
			NSString *msg = @"The address param is nil. Should be a valid address.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		// Create the socket(s) if needed
		
		if ((flags & kDidCreateSockets) == 0)
		{
			if (![self createSockets:&err])
			{
				return_from_block;
			}
		}
		
		// The remoteAddr parameter could be of type NSMutableData.
		// So we copy it to be safe.
		
		NSData *address = [remoteAddr copy];
		NSArray *addresses = [NSArray arrayWithObject:address];
		
		GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
		packet->addresses = addresses;
		
		// Updates flags, add connect packet to send queue, and pump send queue
		
		flags |= kConnecting;
		
		[sendQueue addObject:packet];
		[self maybeDequeueSend];
		
		result = YES;
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error connecting to address: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

- (void)maybeConnect
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	
	BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]];
	
	if (sendQueueReady)
	{
		GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend;
		
		if (connectPacket->resolveInProgress)
		{
			LogVerbose(@"Waiting for DNS resolve...");
		}
		else
		{
			if (connectPacket->error)
			{
				[self notifyDidNotConnect:connectPacket->error];
			}
			else
			{
				NSData *address = nil;
				NSError *error = nil;
				
				int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses];
				
				// Perform connect
				
				BOOL result = NO;
				
				switch (addressFamily)
				{
					case AF_INET  : result = [self connectWithAddress4:address error:&error]; break;
					case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break;
				}
				
				if (result)
				{
					flags |= kDidBind;
					flags |= kDidConnect;
					
					cachedConnectedAddress = address;
					cachedConnectedHost = [[self class] hostFromAddress:address];
					cachedConnectedPort = [[self class] portFromAddress:address];
					cachedConnectedFamily = addressFamily;
					
					[self notifyDidConnectToAddress:address];
				}
				else
				{
					[self notifyDidNotConnect:error];
				}
			}
			
			flags &= ~kConnecting;
			
			[self endCurrentSend];
			[self maybeDequeueSend];
		}
	}
}

- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	int status = connect(socket4FD, (struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]);
	if (status != 0)
	{
		if (errPtr)
			*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
		
		return NO;
	}
	
	[self closeSocket6];
	flags |= kIPv6Deactivated;
	
	return YES;
}

- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	int status = connect(socket6FD, (struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]);
	if (status != 0)
	{
		if (errPtr)
			*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
		
		return NO;
	}
	
	[self closeSocket4];
	flags |= kIPv4Deactivated;
	
	return YES;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Multicast
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)preJoin:(NSError **)errPtr
{
	if (![self preOp:errPtr])
	{
		return NO;
	}
	
	if (!(flags & kDidBind))
	{
		if (errPtr)
		{
			NSString *msg = @"Must bind a socket before joining a multicast group.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	if ((flags & kConnecting) || (flags & kDidConnect))
	{
		if (errPtr)
		{
			NSString *msg = @"Cannot join a multicast group if connected.";
			*errPtr = [self badConfigError:msg];
		}
		return NO;
	}
	
	return YES;
}

- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
	return [self joinMulticastGroup:group onInterface:nil error:errPtr];
}

- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
    // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP
    return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}

- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
	return [self leaveMulticastGroup:group onInterface:nil error:errPtr];
}

- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
    // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP
    return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}

- (BOOL)performMulticastRequest:(int)requestType
                       forGroup:(NSString *)group
                    onInterface:(NSString *)interface
                          error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		// Run through sanity checks
		
		if (![self preJoin:&err])
		{
			return_from_block;
		}
		
		// Convert group to address
		
		NSData *groupAddr4 = nil;
		NSData *groupAddr6 = nil;
		
		[self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6];
		
		if ((groupAddr4 == nil) && (groupAddr6 == nil))
		{
			NSString *msg = @"Unknown group. Specify valid group IP address.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		// Convert interface to address
		
		NSData *interfaceAddr4 = nil;
		NSData *interfaceAddr6 = nil;
		
		[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
		
		if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil))
		{
			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
		// Perform join
		
		if ((socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
		{
			const struct sockaddr_in *nativeGroup = (struct sockaddr_in *)[groupAddr4 bytes];
			const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes];
			
			struct ip_mreq imreq;
			imreq.imr_multiaddr = nativeGroup->sin_addr;
			imreq.imr_interface = nativeIface->sin_addr;
			
			int status = setsockopt(socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq));
			if (status != 0)
			{
				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
				
				return_from_block;
			}
			
			// Using IPv4 only
			[self closeSocket6];
			
			result = YES;
		}
		else if ((socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
		{
			const struct sockaddr_in6 *nativeGroup = (struct sockaddr_in6 *)[groupAddr6 bytes];
			
			struct ipv6_mreq imreq;
			imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
			imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
			
			int status = setsockopt(socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq));
			if (status != 0)
			{
				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
				
				return_from_block;
			}
			
			// Using IPv6 only
			[self closeSocket4];
			
			result = YES;
		}
		else
		{
			NSString *msg = @"Socket, group, and interface do not have matching IP versions";
			err = [self badParamError:msg];
			
			return_from_block;
		}
		
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Broadcast
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
{
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		if (![self preOp:&err])
		{
			return_from_block;
		}
		
		if ((flags & kDidCreateSockets) == 0)
		{
			if (![self createSockets:&err])
			{
				return_from_block;
			}
		}
		
		if (socket4FD != SOCKET_NULL)
		{
			int value = flag ? 1 : 0;
			int error = setsockopt(socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value));
			
			if (error)
			{
				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
				
				return_from_block;
			}
			result = YES;
		}
		
		// IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
		// The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
		
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Sending
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)sendData:(NSData *)data withTag:(long)tag
{
	[self sendData:data withTimeout:-1.0 tag:tag];
}

- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
	LogTrace();
	
	if ([data length] == 0)
	{
		LogWarn(@"Ignoring attempt to send nil/empty data.");
		return;
	}
	
	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
	
	dispatch_async(socketQueue, ^{ @autoreleasepool {
		
		[sendQueue addObject:packet];
		[self maybeDequeueSend];
	}});
	
}

- (void)sendData:(NSData *)data
          toHost:(NSString *)host
            port:(uint16_t)port
     withTimeout:(NSTimeInterval)timeout
             tag:(long)tag
{
	LogTrace();
	
	if ([data length] == 0)
	{
		LogWarn(@"Ignoring attempt to send nil/empty data.");
		return;
	}
	
	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
	packet->resolveInProgress = YES;
	
	[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
		
		// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
		// and immediately returns. Once the async resolve task completes,
		// this block is executed on our socketQueue.
		
		packet->resolveInProgress = NO;
		
		packet->resolvedAddresses = addresses;
		packet->resolveError = error;
		
		if (packet == currentSend)
		{
			LogVerbose(@"currentSend - address resolved");
			[self doPreSend];
		}
	}];
	
	dispatch_async(socketQueue, ^{ @autoreleasepool {
		
		[sendQueue addObject:packet];
		[self maybeDequeueSend];
		
	}});
	
}

- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
	LogTrace();
	
	if ([data length] == 0)
	{
		LogWarn(@"Ignoring attempt to send nil/empty data.");
		return;
	}
	
	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
	packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr];
	packet->address = remoteAddr;
	
	dispatch_async(socketQueue, ^{ @autoreleasepool {
		
		[sendQueue addObject:packet];
		[self maybeDequeueSend];
	}});
}

- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
{
	[self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
}

- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
            withQueue:(dispatch_queue_t)filterQueue
       isAsynchronous:(BOOL)isAsynchronous
{
	GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL;
	dispatch_queue_t newFilterQueue = NULL;
	
	if (filterBlock)
	{
		NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
		
		newFilterBlock = [filterBlock copy];
		newFilterQueue = filterQueue;
		#if !OS_OBJECT_USE_OBJC
		dispatch_retain(newFilterQueue);
		#endif
	}
	
	dispatch_block_t block = ^{
		
		#if !OS_OBJECT_USE_OBJC
		if (sendFilterQueue) dispatch_release(sendFilterQueue);
		#endif
		
		sendFilterBlock = newFilterBlock;
		sendFilterQueue = newFilterQueue;
		sendFilterAsync = isAsynchronous;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (void)maybeDequeueSend
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	// If we don't have a send operation already in progress
	if (currentSend == nil)
	{
		// Create the sockets if needed
		if ((flags & kDidCreateSockets) == 0)
		{
			NSError *err = nil;
			if (![self createSockets:&err])
			{
				[self closeWithError:err];
				return;
			}
		}
		
		while ([sendQueue count] > 0)
		{
			// Dequeue the next object in the queue
			currentSend = [sendQueue objectAtIndex:0];
			[sendQueue removeObjectAtIndex:0];
			
			if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]])
			{
				[self maybeConnect];
				
				return; // The maybeConnect method, if it connects, will invoke this method again
			}
			else if (currentSend->resolveError)
			{
				// Notify delegate
				[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError];
				
				// Clear currentSend
				currentSend = nil;
				
				continue;
			}
			else
			{
				// Start preprocessing checks on the send packet
				[self doPreSend];
				
				break;
			}
		}
		
		if ((currentSend == nil) && (flags & kCloseAfterSends))
		{
			[self closeWithError:nil];
		}
	}
}

/**
 * This method is called after a sendPacket has been dequeued.
 * It performs various preprocessing checks on the packet,
 * and queries the sendFilter (if set) to determine if the packet can be sent.
 * 
 * If the packet passes all checks, it will be passed on to the doSend method.
**/
- (void)doPreSend
{
	LogTrace();
	
	// 
	// 1. Check for problems with send packet
	// 
	
	BOOL waitingForResolve = NO;
	NSError *error = nil;
	
	if (flags & kDidConnect)
	{
		// Connected socket
		
		if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError)
		{
			NSString *msg = @"Cannot specify destination of packet for connected socket";
			error = [self badConfigError:msg];
		}
		else
		{
			currentSend->address = cachedConnectedAddress;
			currentSend->addressFamily = cachedConnectedFamily;
		}
	}
	else
	{
		// Non-Connected socket
		
		if (currentSend->resolveInProgress)
		{
			// We're waiting for the packet's destination to be resolved.
			waitingForResolve = YES;
		}
		else if (currentSend->resolveError)
		{
			error = currentSend->resolveError;
		}
		else if (currentSend->address == nil)
		{
			if (currentSend->resolvedAddresses == nil)
			{
				NSString *msg = @"You must specify destination of packet for a non-connected socket";
				error = [self badConfigError:msg];
			}
			else
			{
				// Pick the proper address to use (out of possibly several resolved addresses)
				
				NSData *address = nil;
				int addressFamily = AF_UNSPEC;
				
				addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses];
				
				currentSend->address = address;
				currentSend->addressFamily = addressFamily;
			}
		}
	}
	
	if (waitingForResolve)
	{
		// We're waiting for the packet's destination to be resolved.
		
		LogVerbose(@"currentSend - waiting for address resolve");
		
		if (flags & kSock4CanAcceptBytes) {
			[self suspendSend4Source];
		}
		if (flags & kSock6CanAcceptBytes) {
			[self suspendSend6Source];
		}
		
		return;
	}
	
	if (error)
	{
		// Unable to send packet due to some error.
		// Notify delegate and move on.
		
		[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error];
		[self endCurrentSend];
		[self maybeDequeueSend];
		
		return;
	}
	
	// 
	// 2. Query sendFilter (if applicable)
	// 
	
	if (sendFilterBlock && sendFilterQueue)
	{
		// Query sendFilter
		
		if (sendFilterAsync)
		{
			// Scenario 1 of 3 - Need to asynchronously query sendFilter
			
			currentSend->filterInProgress = YES;
			GCDAsyncUdpSendPacket *sendPacket = currentSend;
			
			dispatch_async(sendFilterQueue, ^{ @autoreleasepool {
				
				BOOL allowed = sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag);
				
				dispatch_async(socketQueue, ^{ @autoreleasepool {
					
					sendPacket->filterInProgress = NO;
					if (sendPacket == currentSend)
					{
						if (allowed)
						{
							[self doSend];
						}
						else
						{
							LogVerbose(@"currentSend - silently dropped by sendFilter");
							
							[self notifyDidSendDataWithTag:currentSend->tag];
							[self endCurrentSend];
							[self maybeDequeueSend];
						}
					}
				}});
			}});
		}
		else
		{
			// Scenario 2 of 3 - Need to synchronously query sendFilter
			
			__block BOOL allowed = YES;
			
			dispatch_sync(sendFilterQueue, ^{ @autoreleasepool {
				
				allowed = sendFilterBlock(currentSend->buffer, currentSend->address, currentSend->tag);
			}});
			
			if (allowed)
			{
				[self doSend];
			}
			else
			{
				LogVerbose(@"currentSend - silently dropped by sendFilter");
				
				[self notifyDidSendDataWithTag:currentSend->tag];
				[self endCurrentSend];
				[self maybeDequeueSend];
			}
		}
	}
	else // if (!sendFilterBlock || !sendFilterQueue)
	{
		// Scenario 3 of 3 - No sendFilter. Just go straight into sending.
		
		[self doSend];
	}
}

/**
 * This method performs the actual sending of data in the currentSend packet.
 * It should only be called if the 
**/
- (void)doSend
{
	LogTrace();
	
	NSAssert(currentSend != nil, @"Invalid logic");
	
	// Perform the actual send
	
	ssize_t result = 0;
	
	if (flags & kDidConnect)
	{
		// Connected socket
		
		const void *buffer = [currentSend->buffer bytes];
		size_t length = (size_t)[currentSend->buffer length];
		
		if (currentSend->addressFamily == AF_INET)
		{
			result = send(socket4FD, buffer, length, 0);
			LogVerbose(@"send(socket4FD) = %d", result);
		}
		else
		{
			result = send(socket6FD, buffer, length, 0);
			LogVerbose(@"send(socket6FD) = %d", result);
		}
	}
	else
	{
		// Non-Connected socket
		
		const void *buffer = [currentSend->buffer bytes];
		size_t length = (size_t)[currentSend->buffer length];
		
		const void *dst  = [currentSend->address bytes];
		socklen_t dstSize = (socklen_t)[currentSend->address length];
		
		if (currentSend->addressFamily == AF_INET)
		{
			result = sendto(socket4FD, buffer, length, 0, dst, dstSize);
			LogVerbose(@"sendto(socket4FD) = %d", result);
		}
		else
		{
			result = sendto(socket6FD, buffer, length, 0, dst, dstSize);
			LogVerbose(@"sendto(socket6FD) = %d", result);
		}
	}
	
	// If the socket wasn't bound before, it is now
	
	if ((flags & kDidBind) == 0)
	{
		flags |= kDidBind;
	}
	
	// Check the results.
	// 
	// From the send() & sendto() manpage:
	// 
	// Upon successful completion, the number of bytes which were sent is returned.
	// Otherwise, -1 is returned and the global variable errno is set to indicate the error.
	
	BOOL waitingForSocket = NO;
	NSError *socketError = nil;
	
	if (result == 0)
	{
		waitingForSocket = YES;
	}
	else if (result < 0)
	{
		if (errno == EAGAIN)
			waitingForSocket = YES;
		else
			socketError = [self errnoErrorWithReason:@"Error in send() function."];
	}
	
	if (waitingForSocket)
	{
		// Not enough room in the underlying OS socket send buffer.
		// Wait for a notification of available space.
		
		LogVerbose(@"currentSend - waiting for socket");
		
		if (!(flags & kSock4CanAcceptBytes)) {
			[self resumeSend4Source];
		}
		if (!(flags & kSock6CanAcceptBytes)) {
			[self resumeSend6Source];
		}
		
		if ((sendTimer == NULL) && (currentSend->timeout >= 0.0))
		{
			// Unable to send packet right away.
			// Start timer to timeout the send operation.
			
			[self setupSendTimerWithTimeout:currentSend->timeout];
		}
	}
	else if (socketError)
	{
		[self closeWithError:socketError];
	}
	else // done
	{
		[self notifyDidSendDataWithTag:currentSend->tag];
		[self endCurrentSend];
		[self maybeDequeueSend];
	}
}

/**
 * Releases all resources associated with the currentSend.
**/
- (void)endCurrentSend
{
	if (sendTimer)
	{
		dispatch_source_cancel(sendTimer);
		#if !OS_OBJECT_USE_OBJC
		dispatch_release(sendTimer);
		#endif
		sendTimer = NULL;
	}
	
	currentSend = nil;
}

/**
 * Performs the operations to timeout the current send operation, and move on.
**/
- (void)doSendTimeout
{
	LogTrace();
	
	[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]];
	[self endCurrentSend];
	[self maybeDequeueSend];
}

/**
 * Sets up a timer that fires to timeout the current send operation.
 * This method should only be called once per send packet.
**/
- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout
{
	NSAssert(sendTimer == NULL, @"Invalid logic");
	NSAssert(timeout >= 0.0, @"Invalid logic");
	
	LogTrace();
	
	sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
	
	dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool {
		
		[self doSendTimeout];
	}});
	
	dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
	
	dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0);
	dispatch_resume(sendTimer);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Receiving
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)receiveOnce:(NSError **)errPtr
{
	LogTrace();
	
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{
		
		if ((flags & kReceiveOnce) == 0)
		{
			if ((flags & kDidCreateSockets) == 0)
			{
				NSString *msg = @"Must bind socket before you can receive data. "
				@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
				
				err = [self badConfigError:msg];
				return_from_block;
			}
			
			flags |=  kReceiveOnce;       // Enable
			flags &= ~kReceiveContinuous; // Disable
			
			dispatch_async(socketQueue, ^{ @autoreleasepool {
				
				[self doReceive];
			}});
		}
		
		result = YES;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error in beginReceiving: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

- (BOOL)beginReceiving:(NSError **)errPtr
{
	LogTrace();
	
	__block BOOL result = NO;
	__block NSError *err = nil;
	
	dispatch_block_t block = ^{
		
		if ((flags & kReceiveContinuous) == 0)
		{
			if ((flags & kDidCreateSockets) == 0)
			{
				NSString *msg = @"Must bind socket before you can receive data. "
								@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
				
				err = [self badConfigError:msg];
				return_from_block;
			}
			
			flags |= kReceiveContinuous; // Enable
			flags &= ~kReceiveOnce;      // Disable
			
			dispatch_async(socketQueue, ^{ @autoreleasepool {
				
				[self doReceive];
			}});
		}
		
		result = YES;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
	
	if (err)
		LogError(@"Error in beginReceiving: %@", err);
	
	if (errPtr)
		*errPtr = err;
	
	return result;
}

- (void)pauseReceiving
{
	LogTrace();
	
	dispatch_block_t block = ^{
		
		flags &= ~kReceiveOnce;       // Disable
		flags &= ~kReceiveContinuous; // Disable
		
		if (socket4FDBytesAvailable > 0) {
			[self suspendReceive4Source];
		}
		if (socket6FDBytesAvailable > 0) {
			[self suspendReceive6Source];
		}
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
{
	[self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
}

- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
               withQueue:(dispatch_queue_t)filterQueue
          isAsynchronous:(BOOL)isAsynchronous
{
	GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL;
	dispatch_queue_t newFilterQueue = NULL;
	
	if (filterBlock)
	{
		NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
		
		newFilterBlock = [filterBlock copy];
		newFilterQueue = filterQueue;
		#if !OS_OBJECT_USE_OBJC
		dispatch_retain(newFilterQueue);
		#endif
	}
	
	dispatch_block_t block = ^{
		
		#if !OS_OBJECT_USE_OBJC
		if (receiveFilterQueue) dispatch_release(receiveFilterQueue);
		#endif
		
		receiveFilterBlock = newFilterBlock;
		receiveFilterQueue = newFilterQueue;
		receiveFilterAsync = isAsynchronous;
	};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

- (void)doReceive
{
	LogTrace();
	
	if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0)
	{
		LogVerbose(@"Receiving is paused...");
		
		if (socket4FDBytesAvailable > 0) {
			[self suspendReceive4Source];
		}
		if (socket6FDBytesAvailable > 0) {
			[self suspendReceive6Source];
		}
		
		return;
	}
	
	if ((flags & kReceiveOnce) && (pendingFilterOperations > 0))
	{
		LogVerbose(@"Receiving is temporarily paused (pending filter operations)...");
		
		if (socket4FDBytesAvailable > 0) {
			[self suspendReceive4Source];
		}
		if (socket6FDBytesAvailable > 0) {
			[self suspendReceive6Source];
		}
		
		return;
	}
	
	if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0))
	{
		LogVerbose(@"No data available to receive...");
		
		if (socket4FDBytesAvailable == 0) {
			[self resumeReceive4Source];
		}
		if (socket6FDBytesAvailable == 0) {
			[self resumeReceive6Source];
		}
		
		return;
	}
	
	// Figure out if we should receive on socket4 or socket6
	
	BOOL doReceive4;
	
	if (flags & kDidConnect)
	{
		// Connected socket
		
		doReceive4 = (socket4FD != SOCKET_NULL);
	}
	else
	{
		// Non-Connected socket
		
		if (socket4FDBytesAvailable > 0)
		{
			if (socket6FDBytesAvailable > 0)
			{
				// Bytes available on socket4 & socket6
				
				doReceive4 = (flags & kFlipFlop) ? YES : NO;
				
				flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit)
			}
			else {
				// Bytes available on socket4, but not socket6
				doReceive4 = YES;
			}
		}
		else {
			// Bytes available on socket6, but not socket4
			doReceive4 = NO;
		}
	}
	
	// Perform socket IO
	
	ssize_t result = 0;
	
	NSData *data = nil;
	NSData *addr4 = nil;
	NSData *addr6 = nil;
	
	if (doReceive4)
	{
		NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic");
		LogVerbose(@"Receiving on IPv4");
		
		struct sockaddr_in sockaddr4;
		socklen_t sockaddr4len = sizeof(sockaddr4);
		
		size_t bufSize = MIN(max4ReceiveSize, socket4FDBytesAvailable);
		void *buf = malloc(bufSize);
		
		result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
		LogVerbose(@"recvfrom(socket4FD) = %i", (int)result);
		
		if (result > 0)
		{
			if ((size_t)result >= socket4FDBytesAvailable)
				socket4FDBytesAvailable = 0;
			else
				socket4FDBytesAvailable -= result;
			
			if ((size_t)result != bufSize) {
				buf = realloc(buf, result);
			}
			
			data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
			addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
		}
		else
		{
			LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]);
			socket4FDBytesAvailable = 0;
			free(buf);
		}
	}
	else
	{
		NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic");
		LogVerbose(@"Receiving on IPv6");
		
		struct sockaddr_in6 sockaddr6;
		socklen_t sockaddr6len = sizeof(sockaddr6);
		
		size_t bufSize = MIN(max6ReceiveSize, socket6FDBytesAvailable);
		void *buf = malloc(bufSize);
		
		result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
		LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result);
		
		if (result > 0)
		{
			if ((size_t)result >= socket6FDBytesAvailable)
				socket6FDBytesAvailable = 0;
			else
				socket6FDBytesAvailable -= result;
			
			if ((size_t)result != bufSize) {
				buf = realloc(buf, result);
			}
		
			data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
			addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
		}
		else
		{
			LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]);
			socket6FDBytesAvailable = 0;
			free(buf);
		}
	}
	
	
	BOOL waitingForSocket = NO;
	BOOL notifiedDelegate = NO;
	BOOL ignored = NO;
	
	NSError *socketError = nil;
	
	if (result == 0)
	{
		waitingForSocket = YES;
	}
	else if (result < 0)
	{
		if (errno == EAGAIN)
			waitingForSocket = YES;
		else
			socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"];
	}
	else
	{
		if (flags & kDidConnect)
		{
			if (addr4 && ![self isConnectedToAddress4:addr4])
				ignored = YES;
			if (addr6 && ![self isConnectedToAddress6:addr6])
				ignored = YES;
		}
		
		NSData *addr = (addr4 != nil) ? addr4 : addr6;
		
		if (!ignored)
		{
			if (receiveFilterBlock && receiveFilterQueue)
			{
				// Run data through filter, and if approved, notify delegate
				
				__block id filterContext = nil;
				__block BOOL allowed = NO;
				
				if (receiveFilterAsync)
				{
					pendingFilterOperations++;
					dispatch_async(receiveFilterQueue, ^{ @autoreleasepool {
						
						allowed = receiveFilterBlock(data, addr, &filterContext);
						
						// Transition back to socketQueue to get the current delegate / delegateQueue
						dispatch_async(socketQueue, ^{ @autoreleasepool {
							
							pendingFilterOperations--;
							
							if (allowed)
							{
								[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
							}
							else
							{
								LogVerbose(@"received packet silently dropped by receiveFilter");
							}
							
							if (flags & kReceiveOnce)
							{
								if (allowed)
								{
									// The delegate has been notified,
									// so our receive once operation has completed.
									flags &= ~kReceiveOnce;
								}
								else if (pendingFilterOperations == 0)
								{
									// All pending filter operations have completed,
									// and none were allowed through.
									// Our receive once operation hasn't completed yet.
									[self doReceive];
								}
							}
						}});
					}});
				}
				else // if (!receiveFilterAsync)
				{
					dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool {
						
						allowed = receiveFilterBlock(data, addr, &filterContext);
					}});
					
					if (allowed)
					{
						[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
						notifiedDelegate = YES;
					}
					else
					{
						LogVerbose(@"received packet silently dropped by receiveFilter");
						ignored = YES;
					}
				}
			}
			else // if (!receiveFilterBlock || !receiveFilterQueue)
			{
				[self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil];
				notifiedDelegate = YES;
			}
		}
	}
	
	if (waitingForSocket)
	{
		// Wait for a notification of available data.
		
		if (socket4FDBytesAvailable == 0) {
			[self resumeReceive4Source];
		}
		if (socket6FDBytesAvailable == 0) {
			[self resumeReceive6Source];
		}
	}
	else if (socketError)
	{
		[self closeWithError:socketError];
	}
	else
	{
		if (flags & kReceiveContinuous)
		{
			// Continuous receive mode
			[self doReceive];
		}
		else
		{
			// One-at-a-time receive mode
			if (notifiedDelegate)
			{
				// The delegate has been notified (no set filter).
				// So our receive once operation has completed.
				flags &= ~kReceiveOnce;
			}
			else if (ignored)
			{
				[self doReceive];
			}
			else
			{
				// Waiting on asynchronous receive filter...
			}
		}
	}
}

- (void)doReceiveEOF
{
	LogTrace();
	
	[self closeWithError:[self socketClosedError]];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Closing
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (void)closeWithError:(NSError *)error
{
	LogVerbose(@"closeWithError: %@", error);
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	if (currentSend) [self endCurrentSend];
	
	[sendQueue removeAllObjects];
	
	// If a socket has been created, we should notify the delegate.
	BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO;
	
	// Close all sockets, send/receive sources, cfstreams, etc
#if TARGET_OS_IPHONE
	[self removeStreamsFromRunLoop];
	[self closeReadAndWriteStreams];
#endif
	[self closeSockets];
	
	// Clear all flags (config remains as is)
	flags = 0;
	
	if (shouldCallDelegate)
	{
		[self notifyDidCloseWithError:error];
	}
}

- (void)close
{
	LogTrace();
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		[self closeWithError:nil];
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
}

- (void)closeAfterSending
{
	LogTrace();
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		flags |= kCloseAfterSends;
		
		if (currentSend == nil && [sendQueue count] == 0)
		{
			[self closeWithError:nil];
		}
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

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

#if TARGET_OS_IPHONE

static NSThread *listenerThread;

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

+ (void)startListenerThreadIfNeeded
{
	static dispatch_once_t predicate;
	dispatch_once(&predicate, ^{
		
		listenerThread = [[NSThread alloc] initWithTarget:self
		                                         selector:@selector(listenerThread)
		                                           object:nil];
		[listenerThread start];
	});
}

+ (void)listenerThread
{
	@autoreleasepool {
	
		[[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName];
		
		LogInfo(@"ListenerThread: Started");
		
		// We can't run the run loop unless it has an associated input source or a timer.
		// So we'll just create a timer that will never fire - unless the server runs for a decades.
		[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
		                                 target:self
		                               selector:@selector(ignore:)
		                               userInfo:nil
		                                repeats:YES];
		
		[[NSRunLoop currentRunLoop] run];
		
		LogInfo(@"ListenerThread: Stopped");
	}
}

+ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
{
	LogTrace();
	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
	
	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
	
	if (asyncUdpSocket->readStream4)
		CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->readStream6)
		CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->writeStream4)
		CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->writeStream6)
		CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
}

+ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
{
	LogTrace();
	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
	
	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
	
	if (asyncUdpSocket->readStream4)
		CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->readStream6)
		CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->writeStream4)
		CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
	
	if (asyncUdpSocket->writeStream6)
		CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
}

static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
	@autoreleasepool {
		GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
	
		switch(type)
		{
			case kCFStreamEventOpenCompleted:
			{
				LogCVerbose(@"CFReadStreamCallback - Open");
				break;
			}
			case kCFStreamEventHasBytesAvailable:
			{
				LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
				break;
			}
			case kCFStreamEventErrorOccurred:
			case kCFStreamEventEndEncountered:
			{
				NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
				if (error == nil && type == kCFStreamEventEndEncountered)
				{
					error = [asyncUdpSocket socketClosedError];
				}
				
				dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
					
					LogCVerbose(@"CFReadStreamCallback - %@",
					             (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
					
					if (stream != asyncUdpSocket->readStream4 &&
					    stream != asyncUdpSocket->readStream6  )
					{
						LogCVerbose(@"CFReadStreamCallback - Ignored");
						return_from_block;
					}
					
					[asyncUdpSocket closeWithError:error];
					
				}});
				
				break;
			}
			default:
			{
				LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type);
			}
		}
	}
}

static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
{
	@autoreleasepool {
		GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
		
		switch(type)
		{
			case kCFStreamEventOpenCompleted:
			{
				LogCVerbose(@"CFWriteStreamCallback - Open");
				break;
			}
			case kCFStreamEventCanAcceptBytes:
			{
				LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
				break;
			}
			case kCFStreamEventErrorOccurred:
			case kCFStreamEventEndEncountered:
			{
				NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
				if (error == nil && type == kCFStreamEventEndEncountered)
				{
					error = [asyncUdpSocket socketClosedError];
				}
				
				dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
					
					LogCVerbose(@"CFWriteStreamCallback - %@",
					             (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
					
					if (stream != asyncUdpSocket->writeStream4 &&
					    stream != asyncUdpSocket->writeStream6  )
					{
						LogCVerbose(@"CFWriteStreamCallback - Ignored");
						return_from_block;
					}
					
					[asyncUdpSocket closeWithError:error];
					
				}});
				
				break;
			}
			default:
			{
				LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type);
			}
		}
	}
}

- (BOOL)createReadAndWriteStreams:(NSError **)errPtr
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	NSError *err = nil;
	
	if (readStream4 || writeStream4 || readStream6 || writeStream6)
	{
		// Streams already created
		return YES;
	}
	
	if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
	{
		err = [self otherError:@"Cannot create streams without a file descriptor"];
		goto Failed;
	}
	
	// Create streams
	
	LogVerbose(@"Creating read and write stream(s)...");
	
	if (socket4FD != SOCKET_NULL)
	{
		CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4);
		if (!readStream4 || !writeStream4)
		{
			err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"];
			goto Failed;
		}
	}
	
	if (socket6FD != SOCKET_NULL)
	{
		CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6);
		if (!readStream6 || !writeStream6)
		{
			err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"];
			goto Failed;
		}
	}
	
	// Ensure the CFStream's don't close our underlying socket
	
	CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
	CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
	
	CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
	CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
	
	return YES;
	
Failed:
	if (readStream4)
	{
		CFReadStreamClose(readStream4);
		CFRelease(readStream4);
		readStream4 = NULL;
	}
	if (writeStream4)
	{
		CFWriteStreamClose(writeStream4);
		CFRelease(writeStream4);
		writeStream4 = NULL;
	}
	if (readStream6)
	{
		CFReadStreamClose(readStream6);
		CFRelease(readStream6);
		readStream6 = NULL;
	}
	if (writeStream6)
	{
		CFWriteStreamClose(writeStream6);
		CFRelease(writeStream6);
		writeStream6 = NULL;
	}
	
	if (errPtr)
		*errPtr = err;
	
	return NO;
}

- (BOOL)registerForStreamCallbacks:(NSError **)errPtr
{
	LogTrace();
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
	
	NSError *err = nil;
	
	streamContext.version = 0;
	streamContext.info = (__bridge void *)self;
	streamContext.retain = nil;
	streamContext.release = nil;
	streamContext.copyDescription = nil;
	
	CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
	CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
	
//	readStreamEvents  |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable);
//	writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes);
	
	if (socket4FD != SOCKET_NULL)
	{
		if (readStream4 == NULL || writeStream4 == NULL)
		{
			err = [self otherError:@"Read/Write stream4 is null"];
			goto Failed;
		}
		
		BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext);
		BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
		
		if (!r1 || !r2)
		{
			err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"];
			goto Failed;
		}
	}
	
	if (socket6FD != SOCKET_NULL)
	{
		if (readStream6 == NULL || writeStream6 == NULL)
		{
			err = [self otherError:@"Read/Write stream6 is null"];
			goto Failed;
		}
		
		BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext);
		BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
		
		if (!r1 || !r2)
		{
			err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"];
			goto Failed;
		}
	}
	
	return YES;
	
Failed:
	if (readStream4) {
		CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
	}
	if (writeStream4) {
		CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
	}
	if (readStream6) {
		CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
	}
	if (writeStream6) {
		CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
	}
	
	if (errPtr) *errPtr = err;
	return NO;
}

- (BOOL)addStreamsToRunLoop:(NSError **)errPtr
{
	LogTrace();
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
	
	if (!(flags & kAddedStreamListener))
	{
		[[self class] startListenerThreadIfNeeded];
		[[self class] performSelector:@selector(addStreamListener:)
		                     onThread:listenerThread
		                   withObject:self
		                waitUntilDone:YES];
		
		flags |= kAddedStreamListener;
	}
	
	return YES;
}

- (BOOL)openStreams:(NSError **)errPtr
{
	LogTrace();
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
	
	NSError *err = nil;
	
	if (socket4FD != SOCKET_NULL)
	{
		BOOL r1 = CFReadStreamOpen(readStream4);
		BOOL r2 = CFWriteStreamOpen(writeStream4);
		
		if (!r1 || !r2)
		{
			err = [self otherError:@"Error in CFStreamOpen() [IPv4]"];
			goto Failed;
		}
	}
	
	if (socket6FD != SOCKET_NULL)
	{
		BOOL r1 = CFReadStreamOpen(readStream6);
		BOOL r2 = CFWriteStreamOpen(writeStream6);
		
		if (!r1 || !r2)
		{
			err = [self otherError:@"Error in CFStreamOpen() [IPv6]"];
			goto Failed;
		}
	}
	
	return YES;
	
Failed:
	if (errPtr) *errPtr = err;
	return NO;
}

- (void)removeStreamsFromRunLoop
{
	LogTrace();
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	if (flags & kAddedStreamListener)
	{
		[[self class] performSelector:@selector(removeStreamListener:)
		                     onThread:listenerThread
		                   withObject:self
		                waitUntilDone:YES];
		
		flags &= ~kAddedStreamListener;
	}
}

- (void)closeReadAndWriteStreams
{
	LogTrace();
	
	if (readStream4)
	{
		CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
		CFReadStreamClose(readStream4);
		CFRelease(readStream4);
		readStream4 = NULL;
	}
	if (writeStream4)
	{
		CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
		CFWriteStreamClose(writeStream4);
		CFRelease(writeStream4);
		writeStream4 = NULL;
	}
	if (readStream6)
	{
		CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
		CFReadStreamClose(readStream6);
		CFRelease(readStream6);
		readStream6 = NULL;
	}
	if (writeStream6)
	{
		CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
		CFWriteStreamClose(writeStream6);
		CFRelease(writeStream6);
		writeStream6 = NULL;
	}
}

#endif

- (void)applicationWillEnterForeground:(NSNotification *)notification
{
	LogTrace();
	
	// If the application was backgrounded, then iOS may have shut down our sockets.
	// So we take a quick look to see if any of them received an EOF.
	
	dispatch_block_t block = ^{ @autoreleasepool {
		
		[self resumeReceive4Source];
		[self resumeReceive6Source];
	}};
	
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_async(socketQueue, block);
}

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

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

- (void)performBlock:(dispatch_block_t)block
{
	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
		block();
	else
		dispatch_sync(socketQueue, block);
}

- (int)socketFD
{
	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
				THIS_FILE, THIS_METHOD);
		return SOCKET_NULL;
	}
	
	if (socket4FD != SOCKET_NULL)
		return socket4FD;
	else
		return socket6FD;
}

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

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

#if TARGET_OS_IPHONE

- (CFReadStreamRef)readStream
{
	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
				THIS_FILE, THIS_METHOD);
		return NULL;
	}
	
	NSError *err = nil;
	if (![self createReadAndWriteStreams:&err])
	{
		LogError(@"Error creating CFStream(s): %@", err);
		return NULL;
	}
	
	// Todo...
	
	if (readStream4)
		return readStream4;
	else
		return readStream6;
}

- (CFWriteStreamRef)writeStream
{
	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
				THIS_FILE, THIS_METHOD);
		return NULL;
	}
	
	NSError *err = nil;
	if (![self createReadAndWriteStreams:&err])
	{
		LogError(@"Error creating CFStream(s): %@", err);
		return NULL;
	}
	
	if (writeStream4)
		return writeStream4;
	else
		return writeStream6;
}

- (BOOL)enableBackgroundingOnSockets
{
	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
	{
		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
				THIS_FILE, THIS_METHOD);
		return NO;
	}
	
	// Why is this commented out?
	// See comments below.
	
//	NSError *err = nil;
//	if (![self createReadAndWriteStreams:&err])
//	{
//		LogError(@"Error creating CFStream(s): %@", err);
//		return NO;
//	}
//	
//	LogVerbose(@"Enabling backgrouding on socket");
//	
//	BOOL r1, r2;
//	
//	if (readStream4 && writeStream4)
//	{
//		r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//		r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//		
//		if (!r1 || !r2)
//		{
//			LogError(@"Error setting voip type (IPv4)");
//			return NO;
//		}
//	}
//	
//	if (readStream6 && writeStream6)
//	{
//		r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//		r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//		
//		if (!r1 || !r2)
//		{
//			LogError(@"Error setting voip type (IPv6)");
//			return NO;
//		}
//	}
//	
//	return YES;
	
	// The above code will actually appear to work.
	// The methods will return YES, and everything will appear fine.
	// 
	// One tiny problem: the sockets will still get closed when the app gets backgrounded.
	// 
	// Apple does not officially support backgrounding UDP sockets.
	
	return NO;
}

#endif

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

+ (NSString *)hostFromAddress:(NSData *)address
{
	NSString *host = nil;
	[self getHost:&host port:NULL family:NULL fromAddress:address];
	
	return host;
}

+ (uint16_t)portFromAddress:(NSData *)address
{
	uint16_t port = 0;
	[self getHost:NULL port:&port family:NULL fromAddress:address];
	
	return port;
}

+ (int)familyFromAddress:(NSData *)address
{
	int af = AF_UNSPEC;
	[self getHost:NULL port:NULL family:&af fromAddress:address];
	
	return af;
}

+ (BOOL)isIPv4Address:(NSData *)address
{
	int af = AF_UNSPEC;
	[self getHost:NULL port:NULL family:&af fromAddress:address];
	
	return (af == AF_INET);
}

+ (BOOL)isIPv6Address:(NSData *)address
{
	int af = AF_UNSPEC;
	[self getHost:NULL port:NULL family:&af fromAddress:address];
	
	return (af == AF_INET6);
}

+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
{
	return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
}

+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address
{
	if ([address length] >= sizeof(struct sockaddr))
	{
		const struct sockaddr *addrX = (const struct sockaddr *)[address bytes];
		
		if (addrX->sa_family == AF_INET)
		{
			if ([address length] >= sizeof(struct sockaddr_in))
			{
				const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addrX;
				
				if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4];
				if (portPtr) *portPtr = [self portFromSockaddr4:addr4];
				if (afPtr)   *afPtr   = AF_INET;
				
				return YES;
			}
		}
		else if (addrX->sa_family == AF_INET6)
		{
			if ([address length] >= sizeof(struct sockaddr_in6))
			{
				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addrX;
				
				if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6];
				if (portPtr) *portPtr = [self portFromSockaddr6:addr6];
				if (afPtr)   *afPtr   = AF_INET6;
				
				return YES;
			}
		}
	}
	
	if (hostPtr) *hostPtr = nil;
	if (portPtr) *portPtr = 0;
	if (afPtr)   *afPtr   = AF_UNSPEC;
	
	return NO;
}

@end
