diff --git a/.gitignore b/.gitignore
index 86f21d8..9aa45ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ DerivedData/
*.perspectivev3
!default.perspectivev3
xcuserdata/
+xcuserdata/*
## Other
*.moved-aside
@@ -58,3 +59,5 @@ fastlane/screenshots
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
+.DS_Store
+
diff --git a/needle-agent/Podfile b/needle-agent/Podfile
new file mode 100755
index 0000000..d0807d4
--- /dev/null
+++ b/needle-agent/Podfile
@@ -0,0 +1,10 @@
+# Uncomment this line to define a global platform for your project
+ platform :ios, '8.1'
+
+target 'needleAgent' do
+ # Uncomment this line if you're using Swift or would like to use dynamic frameworks
+ use_frameworks!
+ # Pods for needleAgent
+ pod 'CocoaAsyncSocket'
+
+end
diff --git a/needle-agent/Podfile.lock b/needle-agent/Podfile.lock
new file mode 100755
index 0000000..b5cfcfd
--- /dev/null
+++ b/needle-agent/Podfile.lock
@@ -0,0 +1,14 @@
+PODS:
+ - CocoaAsyncSocket (7.5.0):
+ - CocoaAsyncSocket/GCD (= 7.5.0)
+ - CocoaAsyncSocket/GCD (7.5.0)
+
+DEPENDENCIES:
+ - CocoaAsyncSocket
+
+SPEC CHECKSUMS:
+ CocoaAsyncSocket: 3baeb1ddd969f81cf9fca81053ae49ef2d1cbbfa
+
+PODFILE CHECKSUM: 7c5957ad98cb9e0ab53e09b0d15e8d0fa3d69ccb
+
+COCOAPODS: 1.0.1
diff --git a/needle-agent/Pods/CocoaAsyncSocket/README.markdown b/needle-agent/Pods/CocoaAsyncSocket/README.markdown
new file mode 100755
index 0000000..824d126
--- /dev/null
+++ b/needle-agent/Pods/CocoaAsyncSocket/README.markdown
@@ -0,0 +1,110 @@
+# CocoaAsyncSocket
+[![Build Status](https://travis-ci.org/robbiehanson/CocoaAsyncSocket.svg?branch=master)](https://travis-ci.org/robbiehanson/CocoaAsyncSocket) [![Version Status](https://img.shields.io/cocoapods/v/CocoaAsyncSocket.svg?style=flat)](http://cocoadocs.org/docsets/CocoaAsyncSocket) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](http://img.shields.io/cocoapods/p/CocoaAsyncSocket.svg?style=flat)](http://cocoapods.org/?q=CocoaAsyncSocket) [![license Public Domain](https://img.shields.io/badge/license-Public%20Domain-orange.svg?style=flat)](https://en.wikipedia.org/wiki/Public_domain)
+
+
+CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for Mac and iOS. The classes are described below.
+
+## Installation
+
+#### CocoaPods
+
+Install using [CocoaPods](http://cocoapods.org) by adding this line to your Podfile:
+
+````ruby
+use_frameworks! # Add this if you are targeting iOS 8+ or using Swift
+pod 'CocoaAsyncSocket'
+````
+
+#### Carthage
+
+CocoaAsyncSocket is [Carthage](https://github.com/Carthage/Carthage) compatible. To include it add the following line to your `Cartfile`
+
+```bash
+github "robbiehanson/CocoaAsyncSocket" "master"
+```
+
+The project is currently configured to build for **iOS**, **tvOS** and **Mac**. After building with carthage the resultant frameworks will be stored in:
+
+* `Carthage/Build/iOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/tvOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/Mac/CocoaAsyncSocket.framework`
+
+Select the correct framework(s) and drag it into your project.
+
+#### Manual
+
+You can also include it into your project by adding the source files directly, but you should probably be using a dependency manager to keep up to date.
+
+### Importing
+
+Using Objective-C:
+
+```obj-c
+// When using iOS 8+ frameworks
+@import CocoaAsyncSocket;
+
+// OR when not using frameworks, targeting iOS 7 or below
+#import "GCDAsyncSocket.h" // for TCP
+#import "GCDAsyncUdpSocket.h" // for UDP
+```
+
+Using Swift:
+
+```swift
+import CocoaAsyncSocket
+```
+
+## TCP
+
+**GCDAsyncSocket** is a TCP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native objective-c, fully self-contained in one class.
+ _No need to muck around with sockets or streams. This class handles everything for you._
+
+- Full delegate support
+ _Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking reads and writes, with optional timeouts.
+ _You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._
+
+- Automatic socket acceptance.
+ _Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._
+
+- Support for TCP streams over IPv4 and IPv6.
+ _Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._
+
+- Support for TLS / SSL
+ _Secure your socket with ease using just a single method call. Available for both client and server sockets._
+
+- Fully GCD based and Thread-Safe
+ _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+- The Latest Technology & Performance Optimizations
+ _Internally the library takes advantage of technologies such as [kqueue's](http://en.wikipedia.org/wiki/Kqueue) to limit [system calls](http://en.wikipedia.org/wiki/System_call) and optimize buffer allocations. In other words, peak performance._
+
+## UDP
+
+**GCDAsyncUdpSocket** is a UDP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native objective-c, fully self-contained in one class.
+ _No need to muck around with low-level sockets. This class handles everything for you._
+
+- Full delegate support.
+ _Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking send and receive operations, with optional timeouts.
+ _You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._
+
+- Support for IPv4 and IPv6.
+ _Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
+
+- Fully GCD based and Thread-Safe
+ _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+***
+
+Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoaasyncsocket)**.
+
+
+Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2)
+
diff --git a/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
new file mode 100755
index 0000000..2d18995
--- /dev/null
+++ b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
@@ -0,0 +1,1210 @@
+//
+// GCDAsyncSocket.h
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson in Q3 2010.
+// Updated and maintained by Deusty LLC and the Apple development community.
+//
+// https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import
+#import
+#import
+#import
+#import
+
+#include // AF_INET, AF_INET6
+
+@class GCDAsyncReadPacket;
+@class GCDAsyncWritePacket;
+@class GCDAsyncSocketPreBuffer;
+@protocol GCDAsyncSocketDelegate;
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString *const GCDAsyncSocketException;
+extern NSString *const GCDAsyncSocketErrorDomain;
+
+extern NSString *const GCDAsyncSocketQueueName;
+extern NSString *const GCDAsyncSocketThreadName;
+
+extern NSString *const GCDAsyncSocketManuallyEvaluateTrust;
+#if TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketUseCFStreamForTLS;
+#endif
+#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName
+#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates
+#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer
+extern NSString *const GCDAsyncSocketSSLPeerID;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
+extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart;
+extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord;
+extern NSString *const GCDAsyncSocketSSLCipherSuites;
+#if !TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
+#endif
+
+#define GCDAsyncSocketLoggingContext 65535
+
+
+typedef NS_ENUM(NSInteger, GCDAsyncSocketError) {
+ GCDAsyncSocketNoError = 0, // Never used
+ GCDAsyncSocketBadConfigError, // Invalid configuration
+ GCDAsyncSocketBadParamError, // Invalid parameter was passed
+ GCDAsyncSocketConnectTimeoutError, // A connect operation timed out
+ GCDAsyncSocketReadTimeoutError, // A read operation timed out
+ GCDAsyncSocketWriteTimeoutError, // A write operation timed out
+ GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing
+ GCDAsyncSocketClosedError, // The remote peer closed the connection
+ GCDAsyncSocketOtherError, // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+@interface GCDAsyncSocket : NSObject
+
+/**
+ * GCDAsyncSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ *
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ *
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
+ * If you choose to provide a socket queue, and the socket queue has a configured target queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ *
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq;
+
+#pragma mark Configuration
+
+@property (atomic, weak, readwrite, nullable) id delegate;
+#if OS_OBJECT_USE_OBJC
+@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue;
+#else
+@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue;
+#endif
+
+- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * If you are setting the delegate to nil within the delegate's dealloc method,
+ * you may need to use the synchronous versions below.
+**/
+- (void)synchronouslySetDelegate:(nullable id)delegate;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ *
+ * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
+ * and can simulataneously accept incoming connections on either protocol.
+ *
+ * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen.
+ * By default, the preferred protocol is IPv4, but may be configured as desired.
+**/
+
+@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled;
+@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled;
+
+@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6;
+
+/**
+ * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555
+ * this is the delay between connecting to the preferred protocol and the fallback protocol.
+ *
+ * Defaults to 300ms.
+**/
+@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally by socket in any way.
+**/
+@property (atomic, strong, readwrite, nullable) id userData;
+
+#pragma mark Accepting
+
+/**
+ * Tells the socket to begin listening and accepting connections on the given port.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ *
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+**/
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as acceptOnPort:error: with the
+ * additional option of specifying which interface to listen on.
+ *
+ * For example, you could specify that the socket should only accept connections over ethernet,
+ * and not other interfaces such as wifi.
+ *
+ * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+ *
+ * You can see the list of interfaces via the command line utility "ifconfig",
+ * or programmatically via the getifaddrs() function.
+ *
+ * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
+**/
+- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Tells the socket to begin listening and accepting connections on the unix domain at the given url.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ *
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+ **/
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects to the given host and port.
+ *
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error:
+ * and uses the default interface, and no timeout.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host and port with an optional timeout.
+ *
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
+**/
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host & port, via the optional interface, with an optional timeout.
+ *
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * The host may also be the special strings "localhost" or "loopback" to specify connecting
+ * to a service on the local machine.
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ *
+ * To not time out use a negative time interval.
+ *
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ *
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ *
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ *
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ viaInterface:(nullable NSString *)interface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * This method invokes connectToAdd
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToAddress:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToAddress:error: method.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, using the specified interface and timeout.
+ *
+ * The address is specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ *
+ * The timeout is optional. To not time out use a negative time interval.
+ *
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ *
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ *
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ *
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr
+ viaInterface:(nullable NSString *)interface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+/**
+ * Connects to the unix domain socket at the given url, using the specified timeout.
+ */
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+#pragma mark Disconnecting
+
+/**
+ * Disconnects immediately (synchronously). Any pending reads or writes are dropped.
+ *
+ * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method
+ * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods).
+ * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns.
+ *
+ * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method)
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket release];
+ *
+ * If you plan on disconnecting the socket, and then immediately asking it to connect again,
+ * you'll likely want to do so like this:
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket setDelegate:self];
+ * [asyncSocket connect...];
+**/
+- (void)disconnect;
+
+/**
+ * Disconnects after all pending reads have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending writes.
+**/
+- (void)disconnectAfterReading;
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting;
+
+/**
+ * Disconnects after all pending reads and writes have completed.
+ * After calling this, the read and write methods will do nothing.
+**/
+- (void)disconnectAfterReadingAndWriting;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns whether the socket is disconnected or connected.
+ *
+ * A disconnected socket may be recycled.
+ * That is, it can used again for connecting or listening.
+ *
+ * If a socket is in the process of connecting, it may be neither disconnected nor connected.
+**/
+@property (atomic, readonly) BOOL isDisconnected;
+@property (atomic, readonly) BOOL isConnected;
+
+/**
+ * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
+ * The host will be an IP address.
+**/
+@property (atomic, readonly, nullable) NSString *connectedHost;
+@property (atomic, readonly) uint16_t connectedPort;
+@property (atomic, readonly, nullable) NSURL *connectedUrl;
+
+@property (atomic, readonly, nullable) NSString *localHost;
+@property (atomic, readonly) uint16_t localPort;
+
+/**
+ * Returns the local or remote address to which this socket is connected,
+ * specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * @seealso connectedHost
+ * @seealso connectedPort
+ * @seealso localHost
+ * @seealso localPort
+**/
+@property (atomic, readonly, nullable) NSData *connectedAddress;
+@property (atomic, readonly, nullable) NSData *localAddress;
+
+/**
+ * Returns whether the socket is IPv4 or IPv6.
+ * An accepting socket may be both.
+**/
+@property (atomic, readonly) BOOL isIPv4;
+@property (atomic, readonly) BOOL isIPv6;
+
+/**
+ * Returns whether or not the socket has been secured via SSL/TLS.
+ *
+ * See also the startTLS method.
+**/
+@property (atomic, readonly) BOOL isSecure;
+
+#pragma mark Reading
+
+// The readData and writeData methods won't block (they are asynchronous).
+//
+// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue.
+// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue.
+//
+// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.)
+// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method
+// is called to optionally allow you to extend the timeout.
+// Upon a timeout, the "socket:didDisconnectWithError:" method is called
+//
+// The tag is for your convenience.
+// You can use it as an array index, step number, state id, pointer, etc.
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, the socket will create a buffer for you.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * A maximum of length bytes will be read.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ * If maxLength is zero, no length restriction is enforced.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If the length is 0, this method does nothing and the delegate is not called.
+**/
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ *
+ * If the length is 0, this method does nothing and the delegate is not called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataToLength:(NSUInteger)length
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ *
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If you pass a maxLength parameter that is less than the length of the data parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ *
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ *
+ * If you pass a maxLength parameter that is less than the length of the data (separator) parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag;
+
+/**
+ * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Writing
+
+/**
+ * Writes data to the socket, and calls the delegate when finished.
+ *
+ * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
+ * If the timeout value is negative, the write operation will not use a timeout.
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
+ * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
+ * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
+ * This is for performance reasons. Often times, if NSMutableData is passed, it is because
+ * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Security
+
+/**
+ * Secures the connection using SSL/TLS.
+ *
+ * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
+ * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
+ * the upgrade to TLS at the same time, without having to wait for the write to finish.
+ * Any reads or writes scheduled after this method is called will occur over the secured connection.
+ *
+ * ==== The available TOP-LEVEL KEYS are:
+ *
+ * - GCDAsyncSocketManuallyEvaluateTrust
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer.
+ * Instead it will pause at the moment evaulation would typically occur,
+ * and allow us to handle the security evaluation however we see fit.
+ * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef.
+ *
+ * Note that if you set this option, then all other configuration keys are ignored.
+ * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method.
+ *
+ * For more information on trust evaluation see:
+ * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation
+ * https://developer.apple.com/library/ios/technotes/tn2232/_index.html
+ *
+ * If unspecified, the default value is NO.
+ *
+ * - GCDAsyncSocketUseCFStreamForTLS (iOS only)
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption.
+ * This gives us more control over the security protocol (many more configuration options),
+ * plus it allows us to optimize things like sys calls and buffer allocation.
+ *
+ * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption
+ * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket
+ * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property
+ * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method.
+ *
+ * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket,
+ * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty.
+ * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings.
+ *
+ * If unspecified, the default value is NO.
+ *
+ * ==== The available CONFIGURATION KEYS are:
+ *
+ * - kCFStreamSSLPeerName
+ * The value must be of type NSString.
+ * It should match the name in the X.509 certificate given by the remote party.
+ * See Apple's documentation for SSLSetPeerDomainName.
+ *
+ * - kCFStreamSSLCertificates
+ * The value must be of type NSArray.
+ * See Apple's documentation for SSLSetCertificate.
+ *
+ * - kCFStreamSSLIsServer
+ * The value must be of type NSNumber, encapsulationg a BOOL value.
+ * See Apple's documentation for SSLCreateContext for iOS.
+ * This is optional for iOS. If not supplied, a NO value is the default.
+ * This is not needed for Mac OS X, and the value is ignored.
+ *
+ * - GCDAsyncSocketSSLPeerID
+ * The value must be of type NSData.
+ * You must set this value if you want to use TLS session resumption.
+ * See Apple's documentation for SSLSetPeerID.
+ *
+ * - GCDAsyncSocketSSLProtocolVersionMin
+ * - GCDAsyncSocketSSLProtocolVersionMax
+ * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value.
+ * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax.
+ * See also the SSLProtocol typedef.
+ *
+ * - GCDAsyncSocketSSLSessionOptionFalseStart
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * See Apple's documentation for kSSLSessionOptionFalseStart.
+ *
+ * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * See Apple's documentation for kSSLSessionOptionSendOneByteRecord.
+ *
+ * - GCDAsyncSocketSSLCipherSuites
+ * The values must be of type NSArray.
+ * Each item within the array must be a NSNumber, encapsulating
+ * See Apple's documentation for SSLSetEnabledCiphers.
+ * See also the SSLCipherSuite typedef.
+ *
+ * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only)
+ * The value must be of type NSData.
+ * See Apple's documentation for SSLSetDiffieHellmanParams.
+ *
+ * ==== The following UNAVAILABLE KEYS are: (with throw an exception)
+ *
+ * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsAnyRoot
+ *
+ * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsExpiredRoots
+ *
+ * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsExpiredCerts
+ *
+ * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetEnableCertVerify
+ *
+ * - kCFStreamSSLLevel (UNAVAILABLE)
+ * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead.
+ * Corresponding deprecated method: SSLSetProtocolVersionEnabled
+ *
+ *
+ * Please refer to Apple's documentation for corresponding SSLFunctions.
+ *
+ * If you pass in nil or an empty dictionary, the default settings will be used.
+ *
+ * IMPORTANT SECURITY NOTE:
+ * The default settings will check to make sure the remote party's certificate is signed by a
+ * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
+ * However it will not verify the name on the certificate unless you
+ * give it a name to verify against via the kCFStreamSSLPeerName key.
+ * The security implications of this are important to understand.
+ * Imagine you are attempting to create a secure connection to MySecureServer.com,
+ * but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
+ * If you simply use the default settings, and MaliciousServer.com has a valid certificate,
+ * the default settings will not detect any problems since the certificate is valid.
+ * To properly secure your connection in this particular scenario you
+ * should set the kCFStreamSSLPeerName property to "MySecureServer.com".
+ *
+ * You can also perform additional validation in socketDidSecure.
+**/
+- (void)startTLS:(nullable NSDictionary *)tlsSettings;
+
+#pragma mark Advanced
+
+/**
+ * Traditionally sockets are not closed until the conversation is over.
+ * However, it is technically possible for the remote enpoint to close its write stream.
+ * Our socket would then be notified that there is no more data to be read,
+ * but our socket would still be writeable and the remote endpoint could continue to receive our data.
+ *
+ * The argument for this confusing functionality stems from the idea that a client could shut down its
+ * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
+ * In practice, however, this technique did little to help server developers.
+ *
+ * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
+ * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
+ * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
+ * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
+ *
+ * In addition to the technical challenges and confusion, many high level socket/stream API's provide
+ * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
+ * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
+ * It might sound like poor design at first, but in fact it simplifies development.
+ *
+ * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
+ * Thus it actually makes sense to close the socket at this point.
+ * And in fact this is what most networking developers want and expect to happen.
+ * However, if you are writing a server that interacts with a plethora of clients,
+ * you might encounter a client that uses the discouraged technique of shutting down its write stream.
+ * If this is the case, you can set this property to NO,
+ * and make use of the socketDidCloseReadStream delegate method.
+ *
+ * The default value is YES.
+**/
+@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;
+
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ * __block BOOL result = NO;
+ * dispatch_block_t block = ^{
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ * }
+ * if (is_executing_on_queue(socketQueue))
+ * block();
+ * else
+ * dispatch_sync(socketQueue, block);
+ *
+ * return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ * dispatch_set_target_queue(socketQueue, ipQueue);
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ * return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ * [clientConnections addObject:newSocket];
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ *
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ *
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ *
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ *
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ *
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's file descriptor(s).
+ * If the socket is a server socket (is accepting incoming connections),
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's internal CFReadStream/CFWriteStream.
+ *
+ * These streams are only used as workarounds for specific iOS shortcomings:
+ *
+ * - Apple has decided to keep the SecureTransport framework private is iOS.
+ * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it.
+ * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream,
+ * instead of the preferred and faster and more powerful SecureTransport.
+ *
+ * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded,
+ * Apple only bothers to notify us via the CFStream API.
+ * The faster and more powerful GCD API isn't notified properly in this case.
+ *
+ * See also: (BOOL)enableBackgroundingOnSocket
+**/
+- (CFReadStreamRef)readStream;
+- (CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ *
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ *
+ * Returns YES if successful, NO otherwise.
+ *
+ * Note: Apple does not officially support backgrounding server sockets.
+ * That is, if your socket is accepting incoming connections, Apple does not officially support
+ * allowing iOS applications to accept incoming connections while an app is backgrounded.
+ *
+ * Example usage:
+ *
+ * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+ * {
+ * [asyncSocket performBlock:^{
+ * [asyncSocket enableBackgroundingOnSocket];
+ * }];
+ * }
+**/
+- (BOOL)enableBackgroundingOnSocket;
+
+#endif
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket.
+**/
+- (SSLContextRef)sslContext;
+
+#pragma mark Utilities
+
+/**
+ * The address lookup utility used by the class.
+ * This method is synchronous, so it's recommended you use it on a background thread/queue.
+ *
+ * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6.
+ *
+ * @returns
+ * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo.
+ * The addresses are specifically for TCP connections.
+ * You can filter the addresses, if needed, using the other utility methods provided by the class.
+**/
++ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Extracting host and port information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address;
+
+/**
+ * A few common line separators, for use with the readDataToData:... methods.
+**/
++ (NSData *)CRLFData; // 0x0D0A
++ (NSData *)CRData; // 0x0D
++ (NSData *)LFData; // 0x0A
++ (NSData *)ZeroData; // 0x00
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol GCDAsyncSocketDelegate
+@optional
+
+/**
+ * This method is called immediately prior to socket:didAcceptNewSocket:.
+ * It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
+ * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
+ *
+ * Since you cannot autorelease a dispatch_queue,
+ * this method uses the "new" prefix in its name to specify that the returned queue has been retained.
+ *
+ * Thus you could do something like this in the implementation:
+ * return dispatch_queue_create("MyQueue", NULL);
+ *
+ * If you are placing multiple sockets on the same queue,
+ * then care should be taken to increment the retain count each time this method is invoked.
+ *
+ * For example, your implementation might look something like this:
+ * dispatch_retain(myExistingQueue);
+ * return myExistingQueue;
+**/
+- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket accepts a connection.
+ * Another socket is automatically spawned to handle it.
+ *
+ * You must retain the newSocket if you wish to handle the connection.
+ * Otherwise the newSocket instance will be released and the spawned connection will be closed.
+ *
+ * By default the new socket will have the same delegate and delegateQueue.
+ * You may, of course, change this at any time.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+ **/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
+
+/**
+ * Called when a socket has completed reading the requested data into memory.
+ * Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+
+/**
+ * Called when a socket has read in data, but has not yet completed the read.
+ * This would occur if using readToData: or readToLength: methods.
+ * It may be used to for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called when a socket has completed writing the requested data. Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
+
+/**
+ * Called when a socket has written some data, but has not yet completed the entire write.
+ * It may be used to for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called if a read operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
+ *
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been read so far for the read operation.
+ *
+ * Note that this method may be called multiple times for a single read if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
+ elapsed:(NSTimeInterval)elapsed
+ bytesDone:(NSUInteger)length;
+
+/**
+ * Called if a write operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
+ *
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been written so far for the write operation.
+ *
+ * Note that this method may be called multiple times for a single write if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
+ elapsed:(NSTimeInterval)elapsed
+ bytesDone:(NSUInteger)length;
+
+/**
+ * Conditionally called if the read stream closes, but the write stream may still be writeable.
+ *
+ * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO.
+ * See the discussion on the autoDisconnectOnClosedReadStream method for more information.
+**/
+- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket disconnects with or without error.
+ *
+ * If you call the disconnect method, and the socket wasn't already disconnected,
+ * then an invocation of this delegate method will be enqueued on the delegateQueue
+ * before the disconnect method returns.
+ *
+ * Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
+ * and the delegate is not also deallocated, then this method will be invoked,
+ * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
+ * This is a generally rare, but is possible if one writes code like this:
+ *
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ *
+ * In this case it may preferrable to nil the delegate beforehand, like this:
+ *
+ * asyncSocket.delegate = nil; // Don't invoke my delegate method
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ *
+ * Of course, this depends on how your state machine is configured.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err;
+
+/**
+ * Called after the socket has successfully completed SSL/TLS negotiation.
+ * This method is not called unless you use the provided startTLS method.
+ *
+ * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close,
+ * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code.
+**/
+- (void)socketDidSecure:(GCDAsyncSocket *)sock;
+
+/**
+ * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to.
+ *
+ * This is only called if startTLS is invoked with options that include:
+ * - GCDAsyncSocketManuallyEvaluateTrust == YES
+ *
+ * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer.
+ *
+ * Note from Apple's documentation:
+ * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain,
+ * [it] might block while attempting network access. You should never call it from your main thread;
+ * call it only from within a function running on a dispatch queue or on a separate thread.
+ *
+ * Thus this method uses a completionHandler block rather than a normal return value.
+ * The completionHandler block is thread-safe, and may be invoked from a background queue/thread.
+ * It is safe to invoke the completionHandler block even if the socket has been closed.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
+ completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
+
+@end
+NS_ASSUME_NONNULL_END
diff --git a/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
new file mode 100755
index 0000000..6d39abb
--- /dev/null
+++ b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
@@ -0,0 +1,8363 @@
+//
+// GCDAsyncSocket.m
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson in Q4 2010.
+// Updated and maintained by Deusty LLC and the Apple development community.
+//
+// https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import "GCDAsyncSocket.h"
+
+#if TARGET_OS_IPHONE
+#import
+#endif
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
+#endif
+
+
+#ifndef GCDAsyncSocketLoggingEnabled
+#define GCDAsyncSocketLoggingEnabled 0
+#endif
+
+#if GCDAsyncSocketLoggingEnabled
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// https://github.com/robbiehanson/CocoaLumberjack
+//
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync YES
+#define LogContext GCDAsyncSocketLoggingContext
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+#ifndef GCDAsyncSocketLogLevel
+#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
+#endif
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = GCDAsyncSocketLogLevel;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...) {}
+#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
+
+
+NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
+NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
+
+NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
+NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
+
+NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust";
+#if TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS";
+#endif
+NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID";
+NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
+NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
+NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart";
+NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord";
+NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+#if !TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
+#endif
+
+enum GCDAsyncSocketFlags
+{
+ kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting)
+ kConnected = 1 << 1, // If set, the socket is connected
+ kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed
+ kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout
+ kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout
+ kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued
+ kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued
+ kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown.
+ kReadSourceSuspended = 1 << 8, // If set, the read source is suspended
+ kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended
+ kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS
+ kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete
+ kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete
+ kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS
+ kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket
+ kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained
+ kDealloc = 1 << 16, // If set, the socket is being deallocated
+#if TARGET_OS_IPHONE
+ kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread
+ kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport
+ kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available
+#endif
+};
+
+enum GCDAsyncSocketConfig
+{
+ kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
+ kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
+ kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4
+ kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes
+};
+
+#if TARGET_OS_IPHONE
+ static NSThread *cfstreamThread; // Used for CFStreams
+
+
+ static uint64_t cfstreamThreadRetainCount; // setup & teardown
+ static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A PreBuffer is used when there is more data available on the socket
+ * than is being requested by current read request.
+ * In this case we slurp up all data from the socket (to minimize sys calls),
+ * and store additional yet unread data in a "prebuffer".
+ *
+ * The prebuffer is entirely drained before we read from the socket again.
+ * In other words, a large chunk of data is written is written to the prebuffer.
+ * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
+ *
+ * A ring buffer was once used for this purpose.
+ * But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
+ * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
+ * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed.
+ *
+ * The current design is very simple and straight-forward, while also keeping memory requirements lower.
+**/
+
+@interface GCDAsyncSocketPreBuffer : NSObject
+{
+ uint8_t *preBuffer;
+ size_t preBufferSize;
+
+ uint8_t *readPointer;
+ uint8_t *writePointer;
+}
+
+- (id)initWithCapacity:(size_t)numBytes;
+
+- (void)ensureCapacityForWrite:(size_t)numBytes;
+
+- (size_t)availableBytes;
+- (uint8_t *)readBuffer;
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr;
+
+- (size_t)availableSpace;
+- (uint8_t *)writeBuffer;
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr;
+
+- (void)didRead:(size_t)bytesRead;
+- (void)didWrite:(size_t)bytesWritten;
+
+- (void)reset;
+
+@end
+
+@implementation GCDAsyncSocketPreBuffer
+
+- (id)initWithCapacity:(size_t)numBytes
+{
+ if ((self = [super init]))
+ {
+ preBufferSize = numBytes;
+ preBuffer = malloc(preBufferSize);
+
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (preBuffer)
+ free(preBuffer);
+}
+
+- (void)ensureCapacityForWrite:(size_t)numBytes
+{
+ size_t availableSpace = [self availableSpace];
+
+ if (numBytes > availableSpace)
+ {
+ size_t additionalBytes = numBytes - availableSpace;
+
+ size_t newPreBufferSize = preBufferSize + additionalBytes;
+ uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
+
+ size_t readPointerOffset = readPointer - preBuffer;
+ size_t writePointerOffset = writePointer - preBuffer;
+
+ preBuffer = newPreBuffer;
+ preBufferSize = newPreBufferSize;
+
+ readPointer = preBuffer + readPointerOffset;
+ writePointer = preBuffer + writePointerOffset;
+ }
+}
+
+- (size_t)availableBytes
+{
+ return writePointer - readPointer;
+}
+
+- (uint8_t *)readBuffer
+{
+ return readPointer;
+}
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
+{
+ if (bufferPtr) *bufferPtr = readPointer;
+ if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
+}
+
+- (void)didRead:(size_t)bytesRead
+{
+ readPointer += bytesRead;
+
+ if (readPointer == writePointer)
+ {
+ // The prebuffer has been drained. Reset pointers.
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+}
+
+- (size_t)availableSpace
+{
+ return preBufferSize - (writePointer - preBuffer);
+}
+
+- (uint8_t *)writeBuffer
+{
+ return writePointer;
+}
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
+{
+ if (bufferPtr) *bufferPtr = writePointer;
+ if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
+}
+
+- (void)didWrite:(size_t)bytesWritten
+{
+ writePointer += bytesWritten;
+}
+
+- (void)reset
+{
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncReadPacket encompasses the instructions for any given read.
+ * The content of a read packet allows the code to determine if we're:
+ * - reading to a certain length
+ * - reading to a certain separator
+ * - or simply reading the first chunk of available data
+**/
+@interface GCDAsyncReadPacket : NSObject
+{
+ @public
+ NSMutableData *buffer;
+ NSUInteger startOffset;
+ NSUInteger bytesDone;
+ NSUInteger maxLength;
+ NSTimeInterval timeout;
+ NSUInteger readLength;
+ NSData *term;
+ BOOL bufferOwner;
+ NSUInteger originalBufferLength;
+ long tag;
+}
+- (id)initWithData:(NSMutableData *)d
+ startOffset:(NSUInteger)s
+ maxLength:(NSUInteger)m
+ timeout:(NSTimeInterval)t
+ readLength:(NSUInteger)l
+ terminator:(NSData *)e
+ tag:(long)i;
+
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
+
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;
+
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
+
+@end
+
+@implementation GCDAsyncReadPacket
+
+- (id)initWithData:(NSMutableData *)d
+ startOffset:(NSUInteger)s
+ maxLength:(NSUInteger)m
+ timeout:(NSTimeInterval)t
+ readLength:(NSUInteger)l
+ terminator:(NSData *)e
+ tag:(long)i
+{
+ if((self = [super init]))
+ {
+ bytesDone = 0;
+ maxLength = m;
+ timeout = t;
+ readLength = l;
+ term = [e copy];
+ tag = i;
+
+ if (d)
+ {
+ buffer = d;
+ startOffset = s;
+ bufferOwner = NO;
+ originalBufferLength = [d length];
+ }
+ else
+ {
+ if (readLength > 0)
+ buffer = [[NSMutableData alloc] initWithLength:readLength];
+ else
+ buffer = [[NSMutableData alloc] initWithLength:0];
+
+ startOffset = 0;
+ bufferOwner = YES;
+ originalBufferLength = 0;
+ }
+ }
+ return self;
+}
+
+/**
+ * Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
+**/
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
+{
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ NSUInteger buffSpace = buffSize - buffUsed;
+
+ if (bytesToRead > buffSpace)
+ {
+ NSUInteger buffInc = bytesToRead - buffSpace;
+
+ [buffer increaseLengthBy:buffInc];
+ }
+}
+
+/**
+ * This method is used when we do NOT know how much data is available to be read from the socket.
+ * This method returns the default value unless it exceeds the specified readLength or maxLength.
+ *
+ * Furthermore, the shouldPreBuffer decision is based upon the packet type,
+ * and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
+**/
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+ NSUInteger result;
+
+ if (readLength > 0)
+ {
+ // Read a specific length of data
+
+ result = MIN(defaultValue, (readLength - bytesDone));
+
+ // There is no need to prebuffer since we know exactly how much data we need to read.
+ // Even if the buffer isn't currently big enough to fit this amount of data,
+ // it would have to be resized eventually anyway.
+
+ if (shouldPreBufferPtr)
+ *shouldPreBufferPtr = NO;
+ }
+ else
+ {
+ // Either reading until we find a specified terminator,
+ // or we're simply reading all available data.
+ //
+ // In other words, one of:
+ //
+ // - readDataToData packet
+ // - readDataWithTimeout packet
+
+ if (maxLength > 0)
+ result = MIN(defaultValue, (maxLength - bytesDone));
+ else
+ result = defaultValue;
+
+ // Since we don't know the size of the read in advance,
+ // the shouldPreBuffer decision is based upon whether the returned value would fit
+ // in the current buffer without requiring a resize of the buffer.
+ //
+ // This is because, in all likelyhood, the amount read from the socket will be less than the default value.
+ // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
+
+ if (shouldPreBufferPtr)
+ {
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ NSUInteger buffSpace = buffSize - buffUsed;
+
+ if (buffSpace >= result)
+ *shouldPreBufferPtr = NO;
+ else
+ *shouldPreBufferPtr = YES;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * For read packets without a set terminator, returns the amount of data
+ * that can be read without exceeding the readLength or maxLength.
+ *
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ *
+ * The given hint MUST be greater than zero.
+**/
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
+{
+ NSAssert(term == nil, @"This method does not apply to term reads");
+ NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+
+ if (readLength > 0)
+ {
+ // Read a specific length of data
+
+ return MIN(bytesAvailable, (readLength - bytesDone));
+
+ // No need to avoid resizing the buffer.
+ // If the user provided their own buffer,
+ // and told us to read a certain length of data that exceeds the size of the buffer,
+ // then it is clear that our code will resize the buffer during the read operation.
+ //
+ // This method does not actually do any resizing.
+ // The resizing will happen elsewhere if needed.
+ }
+ else
+ {
+ // Read all available data
+
+ NSUInteger result = bytesAvailable;
+
+ if (maxLength > 0)
+ {
+ result = MIN(result, (maxLength - bytesDone));
+ }
+
+ // No need to avoid resizing the buffer.
+ // If the user provided their own buffer,
+ // and told us to read all available data without giving us a maxLength,
+ // then it is clear that our code might resize the buffer during the read operation.
+ //
+ // This method does not actually do any resizing.
+ // The resizing will happen elsewhere if needed.
+
+ return result;
+ }
+}
+
+/**
+ * For read packets with a set terminator, returns the amount of data
+ * that can be read without exceeding the maxLength.
+ *
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ *
+ * To optimize memory allocations, mem copies, and mem moves
+ * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
+ * or if the data can be read directly into the read packet's buffer.
+**/
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+ NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+
+
+ NSUInteger result = bytesAvailable;
+
+ if (maxLength > 0)
+ {
+ result = MIN(result, (maxLength - bytesDone));
+ }
+
+ // Should the data be read into the read packet's buffer, or into a pre-buffer first?
+ //
+ // One would imagine the preferred option is the faster one.
+ // So which one is faster?
+ //
+ // Reading directly into the packet's buffer requires:
+ // 1. Possibly resizing packet buffer (malloc/realloc)
+ // 2. Filling buffer (read)
+ // 3. Searching for term (memcmp)
+ // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
+ //
+ // Reading into prebuffer first:
+ // 1. Possibly resizing prebuffer (malloc/realloc)
+ // 2. Filling buffer (read)
+ // 3. Searching for term (memcmp)
+ // 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
+ // 5. Removing underflow from prebuffer (memmove)
+ //
+ // Comparing the performance of the two we can see that reading
+ // data into the prebuffer first is slower due to the extra memove.
+ //
+ // However:
+ // The implementation of NSMutableData is open source via core foundation's CFMutableData.
+ // Decreasing the length of a mutable data object doesn't cause a realloc.
+ // In other words, the capacity of a mutable data object can grow, but doesn't shrink.
+ //
+ // This means the prebuffer will rarely need a realloc.
+ // The packet buffer, on the other hand, may often need a realloc.
+ // This is especially true if we are the buffer owner.
+ // Furthermore, if we are constantly realloc'ing the packet buffer,
+ // and then moving the overflow into the prebuffer,
+ // then we're consistently over-allocating memory for each term read.
+ // And now we get into a bit of a tradeoff between speed and memory utilization.
+ //
+ // The end result is that the two perform very similarly.
+ // And we can answer the original question very simply by another means.
+ //
+ // If we can read all the data directly into the packet's buffer without resizing it first,
+ // then we do so. Otherwise we use the prebuffer.
+
+ if (shouldPreBufferPtr)
+ {
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ if ((buffSize - buffUsed) >= result)
+ *shouldPreBufferPtr = NO;
+ else
+ *shouldPreBufferPtr = YES;
+ }
+
+ return result;
+}
+
+/**
+ * For read packets with a set terminator,
+ * returns the amount of data that can be read from the given preBuffer,
+ * without going over a terminator or the maxLength.
+ *
+ * It is assumed the terminator has not already been read.
+**/
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+ NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!");
+
+ // We know that the terminator, as a whole, doesn't exist in our own buffer.
+ // But it is possible that a _portion_ of it exists in our buffer.
+ // So we're going to look for the terminator starting with a portion of our own buffer.
+ //
+ // Example:
+ //
+ // term length = 3 bytes
+ // bytesDone = 5 bytes
+ // preBuffer length = 5 bytes
+ //
+ // If we append the preBuffer to our buffer,
+ // it would look like this:
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------------------
+ //
+ // So we start our search here:
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // -------^-^-^---------
+ //
+ // And move forwards...
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------^-^-^-------
+ //
+ // Until we find the terminator or reach the end.
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------------^-^-^-
+
+ BOOL found = NO;
+
+ NSUInteger termLength = [term length];
+ NSUInteger preBufferLength = [preBuffer availableBytes];
+
+ if ((bytesDone + preBufferLength) < termLength)
+ {
+ // Not enough data for a full term sequence yet
+ return preBufferLength;
+ }
+
+ NSUInteger maxPreBufferLength;
+ if (maxLength > 0) {
+ maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
+
+ // Note: maxLength >= termLength
+ }
+ else {
+ maxPreBufferLength = preBufferLength;
+ }
+
+ uint8_t seq[termLength];
+ const void *termBuf = [term bytes];
+
+ NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
+ uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
+
+ NSUInteger preLen = termLength - bufLen;
+ const uint8_t *pre = [preBuffer readBuffer];
+
+ NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
+
+ NSUInteger result = maxPreBufferLength;
+
+ NSUInteger i;
+ for (i = 0; i < loopCount; i++)
+ {
+ if (bufLen > 0)
+ {
+ // Combining bytes from buffer and preBuffer
+
+ memcpy(seq, buf, bufLen);
+ memcpy(seq + bufLen, pre, preLen);
+
+ if (memcmp(seq, termBuf, termLength) == 0)
+ {
+ result = preLen;
+ found = YES;
+ break;
+ }
+
+ buf++;
+ bufLen--;
+ preLen++;
+ }
+ else
+ {
+ // Comparing directly from preBuffer
+
+ if (memcmp(pre, termBuf, termLength) == 0)
+ {
+ NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic
+
+ result = preOffset + termLength;
+ found = YES;
+ break;
+ }
+
+ pre++;
+ }
+ }
+
+ // There is no need to avoid resizing the buffer in this particular situation.
+
+ if (foundPtr) *foundPtr = found;
+ return result;
+}
+
+/**
+ * For read packets with a set terminator, scans the packet buffer for the term.
+ * It is assumed the terminator had not been fully read prior to the new bytes.
+ *
+ * If the term is found, the number of excess bytes after the term are returned.
+ * If the term is not found, this method will return -1.
+ *
+ * Note: A return value of zero means the term was found at the very end.
+ *
+ * Prerequisites:
+ * The given number of bytes have been added to the end of our buffer.
+ * Our bytesDone variable has NOT been changed due to the prebuffered bytes.
+**/
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+
+ // The implementation of this method is very similar to the above method.
+ // See the above method for a discussion of the algorithm used here.
+
+ uint8_t *buff = [buffer mutableBytes];
+ NSUInteger buffLength = bytesDone + numBytes;
+
+ const void *termBuff = [term bytes];
+ NSUInteger termLength = [term length];
+
+ // Note: We are dealing with unsigned integers,
+ // so make sure the math doesn't go below zero.
+
+ NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
+
+ while (i + termLength <= buffLength)
+ {
+ uint8_t *subBuffer = buff + startOffset + i;
+
+ if (memcmp(subBuffer, termBuff, termLength) == 0)
+ {
+ return buffLength - (i + termLength);
+ }
+
+ i++;
+ }
+
+ return -1;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncWritePacket encompasses the instructions for any given write.
+**/
+@interface GCDAsyncWritePacket : NSObject
+{
+ @public
+ NSData *buffer;
+ NSUInteger bytesDone;
+ long tag;
+ NSTimeInterval timeout;
+}
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation GCDAsyncWritePacket
+
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+ if((self = [super init]))
+ {
+ buffer = d; // Retain not copy. For performance as documented in header file.
+ bytesDone = 0;
+ timeout = t;
+ tag = i;
+ }
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
+ * This class my be altered to support more than just TLS in the future.
+**/
+@interface GCDAsyncSpecialPacket : NSObject
+{
+ @public
+ NSDictionary *tlsSettings;
+}
+- (id)initWithTLSSettings:(NSDictionary *)settings;
+@end
+
+@implementation GCDAsyncSpecialPacket
+
+- (id)initWithTLSSettings:(NSDictionary *)settings
+{
+ if((self = [super init]))
+ {
+ tlsSettings = [settings copy];
+ }
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncSocket
+{
+ uint32_t flags;
+ uint16_t config;
+
+ __weak id delegate;
+ dispatch_queue_t delegateQueue;
+
+ int socket4FD;
+ int socket6FD;
+ int socketUN;
+ NSURL *socketUrl;
+ int stateIndex;
+ NSData * connectInterface4;
+ NSData * connectInterface6;
+ NSData * connectInterfaceUN;
+
+ dispatch_queue_t socketQueue;
+
+ dispatch_source_t accept4Source;
+ dispatch_source_t accept6Source;
+ dispatch_source_t acceptUNSource;
+ dispatch_source_t connectTimer;
+ dispatch_source_t readSource;
+ dispatch_source_t writeSource;
+ dispatch_source_t readTimer;
+ dispatch_source_t writeTimer;
+
+ NSMutableArray *readQueue;
+ NSMutableArray *writeQueue;
+
+ GCDAsyncReadPacket *currentRead;
+ GCDAsyncWritePacket *currentWrite;
+
+ unsigned long socketFDBytesAvailable;
+
+ GCDAsyncSocketPreBuffer *preBuffer;
+
+#if TARGET_OS_IPHONE
+ CFStreamClientContext streamContext;
+ CFReadStreamRef readStream;
+ CFWriteStreamRef writeStream;
+#endif
+ SSLContextRef sslContext;
+ GCDAsyncSocketPreBuffer *sslPreBuffer;
+ size_t sslWriteCachedLength;
+ OSStatus sslErrCode;
+ OSStatus lastSSLHandshakeError;
+
+ void *IsOnSocketQueueOrTargetQueueKey;
+
+ id userData;
+ NSTimeInterval alternateAddressDelay;
+}
+
+- (id)init
+{
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (id)initWithSocketQueue:(dispatch_queue_t)sq
+{
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+ return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+ if((self = [super init]))
+ {
+ delegate = aDelegate;
+ delegateQueue = dq;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (dq) dispatch_retain(dq);
+ #endif
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
+ socketUrl = nil;
+ stateIndex = 0;
+
+ if (sq)
+ {
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+
+ socketQueue = sq;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(sq);
+ #endif
+ }
+ else
+ {
+ socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
+ }
+
+ // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
+ // From the documentation:
+ //
+ // > Keys are only compared as pointers and are never dereferenced.
+ // > Thus, you can use a pointer to a static variable for a specific subsystem or
+ // > any other value that allows you to identify the value uniquely.
+ //
+ // We're just going to use the memory address of an ivar.
+ // Specifically an ivar that is explicitly named for our purpose to make the code more readable.
+ //
+ // However, it feels tedious (and less readable) to include the "&" all the time:
+ // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
+ //
+ // So we're going to make it so it doesn't matter if we use the '&' or not,
+ // by assigning the value of the ivar to the address of the ivar.
+ // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
+
+ IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
+
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+
+ readQueue = [[NSMutableArray alloc] initWithCapacity:5];
+ currentRead = nil;
+
+ writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
+ currentWrite = nil;
+
+ preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+ alternateAddressDelay = 0.3;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+
+ // Set dealloc flag.
+ // This is used by closeWithError to ensure we don't accidentally retain ourself.
+ flags |= kDealloc;
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ [self closeWithError:nil];
+ }
+ else
+ {
+ dispatch_sync(socketQueue, ^{
+ [self closeWithError:nil];
+ });
+ }
+
+ delegate = nil;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (delegateQueue) dispatch_release(delegateQueue);
+ #endif
+ delegateQueue = NULL;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (socketQueue) dispatch_release(socketQueue);
+ #endif
+ socketQueue = NULL;
+
+ LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return delegate;
+ }
+ else
+ {
+ __block id result;
+
+ 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;
+
+ 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
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kIPv4Disabled) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((config & kIPv4Disabled) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv4Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ 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
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kIPv6Disabled) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((config & kIPv6Disabled) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv6Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ config &= ~kIPv6Disabled;
+ else
+ config |= kIPv6Disabled;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4PreferredOverIPv6
+{
+ // Note: YES means kPreferIPv6 is OFF
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kPreferIPv6) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((config & kPreferIPv6) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv4PreferredOverIPv6:(BOOL)flag
+{
+ // Note: YES means kPreferIPv6 is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ config &= ~kPreferIPv6;
+ else
+ config |= kPreferIPv6;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (NSTimeInterval) alternateAddressDelay {
+ __block NSTimeInterval delay;
+ dispatch_block_t block = ^{
+ delay = alternateAddressDelay;
+ };
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+ return delay;
+}
+
+- (void) setAlternateAddressDelay:(NSTimeInterval)delay {
+ dispatch_block_t block = ^{
+ alternateAddressDelay = delay;
+ };
+ 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 Accepting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
+{
+ return [self acceptOnInterface:nil port:port error:errPtr];
+}
+
+- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in-case interface parameter is immutable.
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ // CreateSocket Block
+ // This block will be invoked within the dispatch block below.
+
+ int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+
+ int socketFD = socket(domain, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ NSString *reason = @"Error in socket() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return SOCKET_NULL;
+ }
+
+ int status;
+
+ // Set socket options
+
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int reuseOn = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling address reuse (setsockopt)";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Bind socket
+
+ status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Listen
+
+ status = listen(socketFD, 1024);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in listen() function";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ return socketFD;
+ };
+
+ // Create dispatch block and run on socketQueue
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (delegate == nil) // Must have delegate set
+ {
+ NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ // Resolve interface from description
+
+ NSMutableData *interface4 = nil;
+ NSMutableData *interface6 = nil;
+
+ [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
+
+ if ((interface4 == nil) && (interface6 == nil))
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv4Disabled && (interface6 == nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && (interface4 == nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
+ BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
+
+ // Create sockets, configure, bind, and listen
+
+ if (enableIPv4)
+ {
+ LogVerbose(@"Creating IPv4 socket");
+ socket4FD = createSocket(AF_INET, interface4);
+
+ if (socket4FD == SOCKET_NULL)
+ {
+ return_from_block;
+ }
+ }
+
+ if (enableIPv6)
+ {
+ LogVerbose(@"Creating IPv6 socket");
+
+ if (enableIPv4 && (port == 0))
+ {
+ // No specific port was specified, so we allowed the OS to pick an available port for us.
+ // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
+
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
+ addr6->sin6_port = htons([self localPort4]);
+ }
+
+ socket6FD = createSocket(AF_INET6, interface6);
+
+ if (socket6FD == SOCKET_NULL)
+ {
+ if (socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(socket4FD);
+ }
+
+ return_from_block;
+ }
+ }
+
+ // Create accept sources
+
+ if (enableIPv4)
+ {
+ accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
+
+ int socketFD = socket4FD;
+ dispatch_source_t acceptSource = accept4Source;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"event4Block");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+
+ #pragma clang diagnostic pop
+ }});
+
+
+ dispatch_source_set_cancel_handler(accept4Source, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(accept4Source)");
+ dispatch_release(acceptSource);
+ #endif
+
+ LogVerbose(@"close(socket4FD)");
+ close(socketFD);
+
+ #pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"dispatch_resume(accept4Source)");
+ dispatch_resume(accept4Source);
+ }
+
+ if (enableIPv6)
+ {
+ accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
+
+ int socketFD = socket6FD;
+ dispatch_source_t acceptSource = accept6Source;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"event6Block");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+
+ #pragma clang diagnostic pop
+ }});
+
+ dispatch_source_set_cancel_handler(accept6Source, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(accept6Source)");
+ dispatch_release(acceptSource);
+ #endif
+
+ LogVerbose(@"close(socket6FD)");
+ close(socketFD);
+
+ #pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"dispatch_resume(accept6Source)");
+ dispatch_resume(accept6Source);
+ }
+
+ flags |= kSocketStarted;
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ LogInfo(@"Error in accept: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ // CreateSocket Block
+ // This block will be invoked within the dispatch block below.
+
+ int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+
+ int socketFD = socket(domain, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ NSString *reason = @"Error in socket() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return SOCKET_NULL;
+ }
+
+ int status;
+
+ // Set socket options
+
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int reuseOn = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling address reuse (setsockopt)";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Bind socket
+
+ status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Listen
+
+ status = listen(socketFD, 1024);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in listen() function";
+ err = [self errnoErrorWithReason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ return socketFD;
+ };
+
+ // Create dispatch block and run on socketQueue
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (delegate == nil) // Must have delegate set
+ {
+ NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ // Remove a previous socket
+
+ NSError *error = nil;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ if ([fileManager fileExistsAtPath:url.path]) {
+ if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) {
+ NSString *msg = @"Could not remove previous unix domain socket at given url.";
+ err = [self otherError:msg];
+
+ return_from_block;
+ }
+ }
+
+ // Resolve interface from description
+
+ NSData *interface = [self getInterfaceAddressFromUrl:url];
+
+ if (interface == nil)
+ {
+ NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Create sockets, configure, bind, and listen
+
+ LogVerbose(@"Creating unix domain socket");
+ socketUN = createSocket(AF_UNIX, interface);
+
+ if (socketUN == SOCKET_NULL)
+ {
+ return_from_block;
+ }
+
+ socketUrl = url;
+
+ // Create accept sources
+
+ acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketUN, 0, socketQueue);
+
+ int socketFD = socketUN;
+ dispatch_source_t acceptSource = acceptUNSource;
+
+ dispatch_source_set_event_handler(acceptUNSource, ^{ @autoreleasepool {
+
+ LogVerbose(@"eventUNBlock");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([self doAccept:socketFD] && (++i < numPendingConnections));
+ }});
+
+ dispatch_source_set_cancel_handler(acceptUNSource, ^{
+
+#if NEEDS_DISPATCH_RETAIN_RELEASE
+ LogVerbose(@"dispatch_release(accept4Source)");
+ dispatch_release(acceptSource);
+#endif
+
+ LogVerbose(@"close(socket4FD)");
+ close(socketFD);
+ });
+
+ LogVerbose(@"dispatch_resume(accept4Source)");
+ dispatch_resume(acceptUNSource);
+
+ flags |= kSocketStarted;
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ LogInfo(@"Error in accept: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)doAccept:(int)parentSocketFD
+{
+ LogTrace();
+
+ int socketType;
+ int childSocketFD;
+ NSData *childSocketAddress;
+
+ if (parentSocketFD == socket4FD)
+ {
+ socketType = 0;
+
+ struct sockaddr_in addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+ else if (parentSocketFD == socket6FD)
+ {
+ socketType = 1;
+
+ struct sockaddr_in6 addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+ else // if (parentSocketFD == socketUN)
+ {
+ socketType = 2;
+
+ struct sockaddr_un addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+
+ // Enable non-blocking IO on the socket
+
+ int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
+ if (result == -1)
+ {
+ LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
+ return NO;
+ }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ // Notify delegate
+
+ if (delegateQueue)
+ {
+ __strong id theDelegate = delegate;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ // Query delegate for custom socket queue
+
+ dispatch_queue_t childSocketQueue = NULL;
+
+ if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
+ {
+ childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
+ onSocket:self];
+ }
+
+ // Create GCDAsyncSocket instance for accepted socket
+
+ GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate
+ delegateQueue:delegateQueue
+ socketQueue:childSocketQueue];
+
+ if (socketType == 0)
+ acceptedSocket->socket4FD = childSocketFD;
+ else if (socketType == 1)
+ acceptedSocket->socket6FD = childSocketFD;
+ else
+ acceptedSocket->socketUN = childSocketFD;
+
+ acceptedSocket->flags = (kSocketStarted | kConnected);
+
+ // Setup read and write sources for accepted socket
+
+ dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool {
+
+ [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
+ }});
+
+ // Notify delegate
+
+ if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
+ {
+ [theDelegate socket:self didAcceptNewSocket:acceptedSocket];
+ }
+
+ // Release the socket queue returned from the delegate (it was retained by acceptedSocket)
+ #if !OS_OBJECT_USE_OBJC
+ if (childSocketQueue) dispatch_release(childSocketQueue);
+ #endif
+
+ // The accepted socket should have been retained by the delegate.
+ // Otherwise it gets properly released when exiting the block.
+ }});
+ }
+
+ return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connection attempt.
+ * It is shared between the connectToHost and connectToAddress methods.
+ *
+**/
+- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (delegate == nil) // Must have delegate set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (interface)
+ {
+ NSMutableData *interface4 = nil;
+ NSMutableData *interface6 = nil;
+
+ [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
+
+ if ((interface4 == nil) && (interface6 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ if (isIPv4Disabled && (interface6 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ if (isIPv6Disabled && (interface4 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ connectInterface4 = interface4;
+ connectInterface6 = interface6;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ return YES;
+}
+
+- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (delegate == nil) // Must have delegate set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ NSData *interface = [self getInterfaceAddressFromUrl:url];
+
+ if (interface == nil)
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ connectInterfaceUN = interface;
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ return YES;
+}
+
+- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+ return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)inHost
+ onPort:(uint16_t)port
+ viaInterface:(NSString *)inInterface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in case immutable objects were passed
+ NSString *host = [inHost copy];
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *preConnectErr = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with host parameter
+
+ if ([host length] == 0)
+ {
+ NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
+ preConnectErr = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithInterface:interface error:&preConnectErr])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ flags |= kSocketStarted;
+
+ LogVerbose(@"Dispatching DNS lookup...");
+
+ // It's possible that the given host parameter is actually a NSMutableString.
+ // So we want to copy it now, within this block that will be executed synchronously.
+ // This way the asynchronous lookup block below doesn't have to worry about it changing.
+
+ NSString *hostCpy = [host copy];
+
+ int aStateIndex = stateIndex;
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ NSError *lookupErr = nil;
+ NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ if (lookupErr)
+ {
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ [strongSelf lookup:aStateIndex didFail:lookupErr];
+ }});
+ }
+ else
+ {
+ NSData *address4 = nil;
+ NSData *address6 = nil;
+
+ for (NSData *address in addresses)
+ {
+ if (!address4 && [[self class] isIPv4Address:address])
+ {
+ address4 = address;
+ }
+ else if (!address6 && [[self class] isIPv6Address:address])
+ {
+ address6 = address;
+ }
+ }
+
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
+ }});
+ }
+
+ #pragma clang diagnostic pop
+ }});
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+
+ if (errPtr) *errPtr = preConnectErr;
+ return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+ return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+ return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)inRemoteAddr
+ viaInterface:(NSString *)inInterface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in case immutable objects were passed
+ NSData *remoteAddr = [inRemoteAddr copy];
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with remoteAddr parameter
+
+ NSData *address4 = nil;
+ NSData *address6 = nil;
+
+ if ([remoteAddr length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
+
+ if (sockaddr->sa_family == AF_INET)
+ {
+ if ([remoteAddr length] == sizeof(struct sockaddr_in))
+ {
+ address4 = remoteAddr;
+ }
+ }
+ else if (sockaddr->sa_family == AF_INET6)
+ {
+ if ([remoteAddr length] == sizeof(struct sockaddr_in6))
+ {
+ address6 = remoteAddr;
+ }
+ }
+ }
+
+ if ((address4 == nil) && (address6 == nil))
+ {
+ NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && (address4 != nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && (address6 != nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithInterface:interface error:&err])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ if (![self connectWithAddress4:address4 address6:address6 error:&err])
+ {
+ return_from_block;
+ }
+
+ flags |= kSocketStarted;
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with host parameter
+
+ if ([url.path length] == 0)
+ {
+ NSString *msg = @"Invalid unix domain socket url.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithUrl:url error:&err])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ flags |= kSocketStarted;
+
+ // Start the normal connection process
+
+ NSError *connectError = nil;
+ if (![self connectWithAddressUN:connectInterfaceUN error:&connectError])
+ {
+ [self closeWithError:connectError];
+
+ return_from_block;
+ }
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(address4 || address6, @"Expected at least one valid address");
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ // Check for problems
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && (address6 == nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ if (isIPv6Disabled && (address4 == nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ // Start the normal connection process
+
+ NSError *err = nil;
+ if (![self connectWithAddress4:address4 address6:address6 error:&err])
+ {
+ [self closeWithError:err];
+ }
+}
+
+/**
+ * This method is called if the DNS lookup fails.
+ * This method is executed on the socketQueue.
+ *
+ * Since the DNS lookup executed synchronously on a global concurrent queue,
+ * the original connection request may have already been cancelled or timed-out by the time this method is invoked.
+ * The lookupIndex tells us whether the lookup is still valid or not.
+**/
+- (void)lookup:(int)aStateIndex didFail:(NSError *)error
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring lookup:didFail: - already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ [self endConnectTimeout];
+ [self closeWithError:error];
+}
+
+- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
+{
+ // Bind the socket to the desired interface (if needed)
+
+ if (connectInterface)
+ {
+ LogVerbose(@"Binding socket...");
+
+ if ([[self class] portFromAddress:connectInterface] > 0)
+ {
+ // Since we're going to be binding to a specific port,
+ // we should turn on reuseaddr to allow us to override sockets in time_wait.
+
+ int reuseOn = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ }
+
+ const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
+
+ int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
+ if (result != 0)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
+
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
+{
+ int socketFD = socket(family, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+
+ return socketFD;
+ }
+
+ if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
+ {
+ [self closeSocket:socketFD];
+
+ return SOCKET_NULL;
+ }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ return socketFD;
+}
+
+- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
+{
+ // If there already is a socket connected, we close socketFD and return
+ if (self.isConnected)
+ {
+ [self closeSocket:socketFD];
+ return;
+ }
+
+ // Start the connection process in a background queue
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ if (strongSelf.isConnected)
+ {
+ [strongSelf closeSocket:socketFD];
+ return_from_block;
+ }
+
+ if (result == 0)
+ {
+ [self closeUnusedSocket:socketFD];
+
+ [strongSelf didConnect:aStateIndex];
+ }
+ else
+ {
+ [strongSelf closeSocket:socketFD];
+
+ // If there are no more sockets trying to connect, we inform the error to the delegate
+ if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
+ {
+ NSError *error = [strongSelf errnoErrorWithReason:@"Error in connect() function"];
+ [strongSelf didNotConnect:aStateIndex error:error];
+ }
+ }
+ }});
+
+#pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"Connecting...");
+}
+
+- (void)closeSocket:(int)socketFD
+{
+ if (socketFD != SOCKET_NULL &&
+ (socketFD == socket6FD || socketFD == socket4FD))
+ {
+ close(socketFD);
+
+ if (socketFD == socket4FD)
+ {
+ LogVerbose(@"close(socket4FD)");
+ socket4FD = SOCKET_NULL;
+ }
+ else if (socketFD == socket6FD)
+ {
+ LogVerbose(@"close(socket6FD)");
+ socket6FD = SOCKET_NULL;
+ }
+ }
+}
+
+- (void)closeUnusedSocket:(int)usedSocketFD
+{
+ if (usedSocketFD != socket4FD)
+ {
+ [self closeSocket:socket4FD];
+ }
+ else if (usedSocketFD != socket6FD)
+ {
+ [self closeSocket:socket6FD];
+ }
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
+ LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
+
+ // Determine socket type
+
+ BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+
+ // Create and bind the sockets
+
+ if (address4)
+ {
+ LogVerbose(@"Creating IPv4 socket");
+
+ socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
+ }
+
+ if (address6)
+ {
+ LogVerbose(@"Creating IPv6 socket");
+
+ socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
+ }
+
+ if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
+ {
+ return NO;
+ }
+
+ int socketFD, alternateSocketFD;
+ NSData *address, *alternateAddress;
+
+ if ((preferIPv6 && socket6FD) || socket4FD == SOCKET_NULL)
+ {
+ socketFD = socket6FD;
+ alternateSocketFD = socket4FD;
+ address = address6;
+ alternateAddress = address4;
+ }
+ else
+ {
+ socketFD = socket4FD;
+ alternateSocketFD = socket6FD;
+ address = address4;
+ alternateAddress = address6;
+ }
+
+ int aStateIndex = stateIndex;
+
+ [self connectSocket:socketFD address:address stateIndex:aStateIndex];
+
+ if (alternateAddress)
+ {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
+ [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
+ });
+ }
+
+ return YES;
+}
+
+- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ // Create the socket
+
+ int socketFD;
+
+ LogVerbose(@"Creating unix domain socket");
+
+ socketUN = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ socketFD = socketUN;
+
+ if (socketFD == SOCKET_NULL)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+
+ return NO;
+ }
+
+ // Bind the socket to the desired interface (if needed)
+
+ LogVerbose(@"Binding socket...");
+
+ int reuseOn = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+
+// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes];
+//
+// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]);
+// if (result != 0)
+// {
+// if (errPtr)
+// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
+//
+// return NO;
+// }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ // Start the connection process in a background queue
+
+ int aStateIndex = stateIndex;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{
+
+ const struct sockaddr *addr = (const struct sockaddr *)[address bytes];
+ int result = connect(socketFD, addr, addr->sa_len);
+ if (result == 0)
+ {
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self didConnect:aStateIndex];
+ }});
+ }
+ else
+ {
+ // TODO: Bad file descriptor
+ perror("connect");
+ NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self didNotConnect:aStateIndex error:error];
+ }});
+ }
+ });
+
+ LogVerbose(@"Connecting...");
+
+ return YES;
+}
+
+- (void)didConnect:(int)aStateIndex
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring didConnect, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ flags |= kConnected;
+
+ [self endConnectTimeout];
+
+ #if TARGET_OS_IPHONE
+ // The endConnectTimeout method executed above incremented the stateIndex.
+ aStateIndex = stateIndex;
+ #endif
+
+ // Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
+ //
+ // Note:
+ // There may be configuration options that must be set by the delegate before opening the streams.
+ // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
+ //
+ // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
+ // This gives the delegate time to properly configure the streams if needed.
+
+ dispatch_block_t SetupStreamsPart1 = ^{
+ #if TARGET_OS_IPHONE
+
+ if (![self createReadAndWriteStream])
+ {
+ [self closeWithError:[self otherError:@"Error creating CFStreams"]];
+ return;
+ }
+
+ if (![self registerForStreamCallbacksIncludingReadWrite:NO])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+ return;
+ }
+
+ #endif
+ };
+ dispatch_block_t SetupStreamsPart2 = ^{
+ #if TARGET_OS_IPHONE
+
+ if (aStateIndex != stateIndex)
+ {
+ // The socket has been disconnected.
+ return;
+ }
+
+ if (![self addStreamsToRunLoop])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+ return;
+ }
+
+ if (![self openStreams])
+ {
+ [self closeWithError:[self otherError:@"Error creating CFStreams"]];
+ return;
+ }
+
+ #endif
+ };
+
+ // Notify delegate
+
+ NSString *host = [self connectedHost];
+ uint16_t port = [self connectedPort];
+ NSURL *url = [self connectedUrl];
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+ {
+ SetupStreamsPart1();
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didConnectToHost:host port:port];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ SetupStreamsPart2();
+ }});
+ }});
+ }
+ else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
+ {
+ SetupStreamsPart1();
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didConnectToUrl:url];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ SetupStreamsPart2();
+ }});
+ }});
+ }
+ else
+ {
+ SetupStreamsPart1();
+ SetupStreamsPart2();
+ }
+
+ // Get the connected socket
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ // Enable non-blocking IO on the socket
+
+ int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (result == -1)
+ {
+ NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
+ [self closeWithError:[self otherError:errMsg]];
+
+ return;
+ }
+
+ // Setup our read/write sources
+
+ [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
+
+ // Dequeue any pending read/write requests
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+}
+
+- (void)didNotConnect:(int)aStateIndex error:(NSError *)error
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring didNotConnect, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ [self closeWithError:error];
+}
+
+- (void)startConnectTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doConnectTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theConnectTimer = connectTimer;
+ dispatch_source_set_cancel_handler(connectTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(connectTimer)");
+ dispatch_release(theConnectTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+ dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ dispatch_resume(connectTimer);
+ }
+}
+
+- (void)endConnectTimeout
+{
+ LogTrace();
+
+ if (connectTimer)
+ {
+ dispatch_source_cancel(connectTimer);
+ connectTimer = NULL;
+ }
+
+ // Increment stateIndex.
+ // This will prevent us from processing results from any related background asynchronous operations.
+ //
+ // Note: This should be called from close method even if connectTimer is NULL.
+ // This is because one might disconnect a socket prior to a successful connection which had no timeout.
+
+ stateIndex++;
+
+ if (connectInterface4)
+ {
+ connectInterface4 = nil;
+ }
+ if (connectInterface6)
+ {
+ connectInterface6 = nil;
+ }
+}
+
+- (void)doConnectTimeout
+{
+ LogTrace();
+
+ [self endConnectTimeout];
+ [self closeWithError:[self connectTimeoutError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Disconnecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ [self endConnectTimeout];
+
+ if (currentRead != nil) [self endCurrentRead];
+ if (currentWrite != nil) [self endCurrentWrite];
+
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ [preBuffer reset];
+
+ #if TARGET_OS_IPHONE
+ {
+ if (readStream || writeStream)
+ {
+ [self removeStreamsFromRunLoop];
+
+ if (readStream)
+ {
+ CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
+ CFReadStreamClose(readStream);
+ CFRelease(readStream);
+ readStream = NULL;
+ }
+ if (writeStream)
+ {
+ CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ writeStream = NULL;
+ }
+ }
+ }
+ #endif
+
+ [sslPreBuffer reset];
+ sslErrCode = lastSSLHandshakeError = noErr;
+
+ if (sslContext)
+ {
+ // Getting a linker error here about the SSLx() functions?
+ // You need to add the Security Framework to your application.
+
+ SSLClose(sslContext);
+
+ #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+ CFRelease(sslContext);
+ #else
+ SSLDisposeContext(sslContext);
+ #endif
+
+ sslContext = NULL;
+ }
+
+ // For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+ // invoke the cancel handler if the dispatch source is paused.
+ // So we have to unpause the source if needed.
+ // This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+
+ if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource)
+ {
+ LogVerbose(@"manually closing close");
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(socket4FD);
+ socket4FD = SOCKET_NULL;
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket6FD)");
+ close(socket6FD);
+ socket6FD = SOCKET_NULL;
+ }
+
+ if (socketUN != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socketUN)");
+ close(socketUN);
+ socketUN = SOCKET_NULL;
+ unlink(socketUrl.path.fileSystemRepresentation);
+ socketUrl = nil;
+ }
+ }
+ else
+ {
+ if (accept4Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept4Source)");
+ dispatch_source_cancel(accept4Source);
+
+ // We never suspend accept4Source
+
+ accept4Source = NULL;
+ }
+
+ if (accept6Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept6Source)");
+ dispatch_source_cancel(accept6Source);
+
+ // We never suspend accept6Source
+
+ accept6Source = NULL;
+ }
+
+ if (acceptUNSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
+ dispatch_source_cancel(acceptUNSource);
+
+ // We never suspend acceptUNSource
+
+ acceptUNSource = NULL;
+ }
+
+ if (readSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(readSource)");
+ dispatch_source_cancel(readSource);
+
+ [self resumeReadSource];
+
+ readSource = NULL;
+ }
+
+ if (writeSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(writeSource)");
+ dispatch_source_cancel(writeSource);
+
+ [self resumeWriteSource];
+
+ writeSource = NULL;
+ }
+
+ // The sockets will be closed by the cancel handlers of the corresponding source
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
+ }
+
+ // If the client has passed the connect/accept method, then the connection has at least begun.
+ // Notify delegate that it is now ending.
+ BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO;
+ BOOL isDeallocating = (flags & kDealloc) ? YES : NO;
+
+ // Clear stored socket info and all flags (config remains as is)
+ socketFDBytesAvailable = 0;
+ flags = 0;
+ sslWriteCachedLength = 0;
+
+ if (shouldCallDelegate)
+ {
+ __strong id theDelegate = delegate;
+ __strong id theSelf = isDeallocating ? nil : self;
+
+ if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidDisconnect:theSelf withError:error];
+ }});
+ }
+ }
+}
+
+- (void)disconnect
+{
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (flags & kSocketStarted)
+ {
+ [self closeWithError:nil];
+ }
+ }};
+
+ // Synchronous disconnection, as documented in the header file
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+- (void)disconnectAfterReading
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (flags & kSocketStarted)
+ {
+ flags |= (kForbidReadsWrites | kDisconnectAfterReads);
+ [self maybeClose];
+ }
+ }});
+}
+
+- (void)disconnectAfterWriting
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (flags & kSocketStarted)
+ {
+ flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
+ [self maybeClose];
+ }
+ }});
+}
+
+- (void)disconnectAfterReadingAndWriting
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (flags & kSocketStarted)
+ {
+ flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
+ [self maybeClose];
+ }
+ }});
+}
+
+/**
+ * Closes the socket if possible.
+ * That is, if all writes have completed, and we're set to disconnect after writing,
+ * or if all reads have completed, and we're set to disconnect after reading.
+**/
+- (void)maybeClose
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ BOOL shouldClose = NO;
+
+ if (flags & kDisconnectAfterReads)
+ {
+ if (([readQueue count] == 0) && (currentRead == nil))
+ {
+ if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ shouldClose = YES;
+ }
+ }
+ else
+ {
+ shouldClose = YES;
+ }
+ }
+ }
+ else if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ shouldClose = YES;
+ }
+ }
+
+ if (shouldClose)
+ {
+ [self closeWithError:nil];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
+}
+
++ (NSError *)gaiError:(int)gai_error
+{
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+ NSDictionary *userInfo = [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 = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
+ reason, NSLocalizedFailureReasonErrorKey, nil];
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+ NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)sslError:(OSStatus)ssl_error
+{
+ NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey];
+
+ return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
+}
+
+- (NSError *)connectTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Attempt to connect to host timed out", nil);
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket maxed out error.
+**/
+- (NSError *)readMaxedOutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Read operation reached set maximum length", nil);
+
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)readTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Read operation timed out", nil);
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)writeTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Write operation timed out", nil);
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
+}
+
+- (NSError *)connectionClosedError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Socket closed by remote peer", nil);
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isDisconnected
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (flags & kSocketStarted) ? NO : YES;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isConnected
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (flags & kConnected) ? YES : NO;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (NSString *)connectedHost
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedHostFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedHostFromSocket6:socket6FD];
+
+ return nil;
+ }
+ else
+ {
+ __block NSString *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (socket4FD != SOCKET_NULL)
+ result = [self connectedHostFromSocket4:socket4FD];
+ else if (socket6FD != SOCKET_NULL)
+ result = [self connectedHostFromSocket6:socket6FD];
+ }});
+
+ return result;
+ }
+}
+
+- (uint16_t)connectedPort
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedPortFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedPortFromSocket6:socket6FD];
+
+ return 0;
+ }
+ else
+ {
+ __block uint16_t result = 0;
+
+ dispatch_sync(socketQueue, ^{
+ // No need for autorelease pool
+
+ if (socket4FD != SOCKET_NULL)
+ result = [self connectedPortFromSocket4:socket4FD];
+ else if (socket6FD != SOCKET_NULL)
+ result = [self connectedPortFromSocket6:socket6FD];
+ });
+
+ return result;
+ }
+}
+
+- (NSURL *)connectedUrl
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socketUN != SOCKET_NULL)
+ return [self connectedUrlFromSocketUN:socketUN];
+
+ return nil;
+ }
+ else
+ {
+ __block NSURL *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (socketUN != SOCKET_NULL)
+ result = [self connectedUrlFromSocketUN:socketUN];
+ }});
+
+ return result;
+ }
+}
+
+- (NSString *)localHost
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self localHostFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self localHostFromSocket6:socket6FD];
+
+ return nil;
+ }
+ else
+ {
+ __block NSString *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (socket4FD != SOCKET_NULL)
+ result = [self localHostFromSocket4:socket4FD];
+ else if (socket6FD != SOCKET_NULL)
+ result = [self localHostFromSocket6:socket6FD];
+ }});
+
+ return result;
+ }
+}
+
+- (uint16_t)localPort
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self localPortFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self localPortFromSocket6:socket6FD];
+
+ return 0;
+ }
+ else
+ {
+ __block uint16_t result = 0;
+
+ dispatch_sync(socketQueue, ^{
+ // No need for autorelease pool
+
+ if (socket4FD != SOCKET_NULL)
+ result = [self localPortFromSocket4:socket4FD];
+ else if (socket6FD != SOCKET_NULL)
+ result = [self localPortFromSocket6:socket6FD];
+ });
+
+ return result;
+ }
+}
+
+- (NSString *)connectedHost4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedHostFromSocket4:socket4FD];
+
+ return nil;
+}
+
+- (NSString *)connectedHost6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedHostFromSocket6:socket6FD];
+
+ return nil;
+}
+
+- (uint16_t)connectedPort4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedPortFromSocket4:socket4FD];
+
+ return 0;
+}
+
+- (uint16_t)connectedPort6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedPortFromSocket6:socket6FD];
+
+ return 0;
+}
+
+- (NSString *)localHost4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self localHostFromSocket4:socket4FD];
+
+ return nil;
+}
+
+- (NSString *)localHost6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self localHostFromSocket6:socket6FD];
+
+ return nil;
+}
+
+- (uint16_t)localPort4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self localPortFromSocket4:socket4FD];
+
+ return 0;
+}
+
+- (uint16_t)localPort6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self localPortFromSocket6:socket6FD];
+
+ return 0;
+}
+
+- (NSString *)connectedHostFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)connectedHostFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)connectedPortFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)connectedPortFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSURL *)connectedUrlFromSocketUN:(int)socketFD
+{
+ struct sockaddr_un sockaddr;
+ socklen_t sockaddrlen = sizeof(sockaddr);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
+ {
+ return 0;
+ }
+ return [[self class] urlFromSockaddrUN:&sockaddr];
+}
+
+- (NSString *)localHostFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)localHostFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)localPortFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)localPortFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSData *)connectedAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+ if (socket4FD != SOCKET_NULL)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (NSData *)localAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+ if (socket4FD != SOCKET_NULL)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPv4
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (socket4FD != SOCKET_NULL);
+ }
+ else
+ {
+ __block BOOL result = NO;
+
+ dispatch_sync(socketQueue, ^{
+ result = (socket4FD != SOCKET_NULL);
+ });
+
+ return result;
+ }
+}
+
+- (BOOL)isIPv6
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (socket6FD != SOCKET_NULL);
+ }
+ else
+ {
+ __block BOOL result = NO;
+
+ dispatch_sync(socketQueue, ^{
+ result = (socket6FD != SOCKET_NULL);
+ });
+
+ return result;
+ }
+}
+
+- (BOOL)isSecure
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (flags & kSocketSecure) ? YES : NO;
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = (flags & kSocketSecure) ? YES : NO;
+ });
+
+ return result;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Finds the address of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+ *
+ * The interface description may optionally contain a port number at the end, separated by a colon.
+ * If a non-zero port parameter is provided, any port number in the interface description is ignored.
+ *
+ * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
+**/
+- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
+ address6:(NSMutableData **)interfaceAddr6Ptr
+ fromDescription:(NSString *)interfaceDescription
+ port:(uint16_t)port
+{
+ NSMutableData *addr4 = nil;
+ NSMutableData *addr6 = nil;
+
+ NSString *interface = nil;
+
+ NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
+ if ([components count] > 0)
+ {
+ NSString *temp = [components objectAtIndex:0];
+ if ([temp length] > 0)
+ {
+ interface = temp;
+ }
+ }
+ if ([components count] > 1 && port == 0)
+ {
+ long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
+
+ if (portL > 0 && portL <= UINT16_MAX)
+ {
+ port = (uint16_t)portL;
+ }
+ }
+
+ if (interface == nil)
+ {
+ // ANY address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(sockaddr4);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(sockaddr6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_any;
+
+ addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
+ {
+ // LOOPBACK address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(sockaddr4);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(sockaddr6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_loopback;
+
+ addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else
+ {
+ const char *iface = [interface UTF8String];
+
+ struct ifaddrs *addrs;
+ const struct ifaddrs *cursor;
+
+ if ((getifaddrs(&addrs) == 0))
+ {
+ cursor = addrs;
+ while (cursor != NULL)
+ {
+ if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+ {
+ // IPv4
+
+ struct sockaddr_in nativeAddr4;
+ memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ else
+ {
+ char ip[INET_ADDRSTRLEN];
+
+ const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ }
+ }
+ else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+ {
+ // IPv6
+
+ struct sockaddr_in6 nativeAddr6;
+ memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ else
+ {
+ char ip[INET6_ADDRSTRLEN];
+
+ const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ }
+ }
+
+ cursor = cursor->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ }
+ }
+
+ if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+ if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url;
+{
+ NSString *path = url.path;
+ if (path.length == 0) {
+ return nil;
+ }
+
+ struct sockaddr_un nativeAddr;
+ nativeAddr.sun_family = AF_UNIX;
+ strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path));
+ nativeAddr.sun_len = SUN_LEN(&nativeAddr);
+ NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)];
+
+ return interface;
+}
+
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
+{
+ readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
+ writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
+
+ // Setup event handlers
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"readEventBlock");
+
+ strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
+ LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable);
+
+ if (strongSelf->socketFDBytesAvailable > 0)
+ [strongSelf doReadData];
+ else
+ [strongSelf doReadEOF];
+
+ #pragma clang diagnostic pop
+ }});
+
+ dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"writeEventBlock");
+
+ strongSelf->flags |= kSocketCanAcceptBytes;
+ [strongSelf doWriteData];
+
+ #pragma clang diagnostic pop
+ }});
+
+ // Setup cancel handlers
+
+ __block int socketFDRefCount = 2;
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theReadSource = readSource;
+ dispatch_source_t theWriteSource = writeSource;
+ #endif
+
+ dispatch_source_set_cancel_handler(readSource, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"readCancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(readSource)");
+ dispatch_release(theReadSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ }
+
+ #pragma clang diagnostic pop
+ });
+
+ dispatch_source_set_cancel_handler(writeSource, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"writeCancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(writeSource)");
+ dispatch_release(theWriteSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ }
+
+ #pragma clang diagnostic pop
+ });
+
+ // We will not be able to read until data arrives.
+ // But we should be able to write immediately.
+
+ socketFDBytesAvailable = 0;
+ flags &= ~kReadSourceSuspended;
+
+ LogVerbose(@"dispatch_resume(readSource)");
+ dispatch_resume(readSource);
+
+ flags |= kSocketCanAcceptBytes;
+ flags |= kWriteSourceSuspended;
+}
+
+- (BOOL)usingCFStreamForTLS
+{
+ #if TARGET_OS_IPHONE
+
+ if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+ {
+ // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+
+ return YES;
+ }
+
+ #endif
+
+ return NO;
+}
+
+- (BOOL)usingSecureTransportForTLS
+{
+ // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable)
+
+ #if TARGET_OS_IPHONE
+
+ if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+ {
+ // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+
+ return NO;
+ }
+
+ #endif
+
+ return YES;
+}
+
+- (void)suspendReadSource
+{
+ if (!(flags & kReadSourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(readSource)");
+
+ dispatch_suspend(readSource);
+ flags |= kReadSourceSuspended;
+ }
+}
+
+- (void)resumeReadSource
+{
+ if (flags & kReadSourceSuspended)
+ {
+ LogVerbose(@"dispatch_resume(readSource)");
+
+ dispatch_resume(readSource);
+ flags &= ~kReadSourceSuspended;
+ }
+}
+
+- (void)suspendWriteSource
+{
+ if (!(flags & kWriteSourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(writeSource)");
+
+ dispatch_suspend(writeSource);
+ flags |= kWriteSourceSuspended;
+ }
+}
+
+- (void)resumeWriteSource
+{
+ if (flags & kWriteSourceSuspended)
+ {
+ LogVerbose(@"dispatch_resume(writeSource)");
+
+ dispatch_resume(writeSource);
+ flags &= ~kWriteSourceSuspended;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reading
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag
+{
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:length
+ timeout:timeout
+ readLength:0
+ terminator:nil
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+ {
+ [readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
+}
+
+- (void)readDataToLength:(NSUInteger)length
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ if (length == 0) {
+ LogWarn(@"Cannot read: length == 0");
+ return;
+ }
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:0
+ timeout:timeout
+ readLength:length
+ terminator:nil
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+ {
+ [readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)maxLength
+ tag:(long)tag
+{
+ if ([data length] == 0) {
+ LogWarn(@"Cannot read: [data length] == 0");
+ return;
+ }
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+ if (maxLength > 0 && maxLength < [data length]) {
+ LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:maxLength
+ timeout:timeout
+ readLength:0
+ terminator:data
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+ {
+ [readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+ __block float result = 0.0F;
+
+ dispatch_block_t block = ^{
+
+ if (!currentRead || ![currentRead isKindOfClass:[GCDAsyncReadPacket class]])
+ {
+ // We're not reading anything right now.
+
+ if (tagPtr != NULL) *tagPtr = 0;
+ if (donePtr != NULL) *donePtr = 0;
+ if (totalPtr != NULL) *totalPtr = 0;
+
+ result = NAN;
+ }
+ else
+ {
+ // It's only possible to know the progress of our read if we're reading to a certain length.
+ // If we're reading to data, we of course have no idea when the data will arrive.
+ // If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
+
+ NSUInteger done = currentRead->bytesDone;
+ NSUInteger total = currentRead->readLength;
+
+ if (tagPtr != NULL) *tagPtr = currentRead->tag;
+ if (donePtr != NULL) *donePtr = done;
+ if (totalPtr != NULL) *totalPtr = total;
+
+ if (total > 0)
+ result = (float)done / (float)total;
+ else
+ result = 1.0F;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+/**
+ * This method starts a new read, if needed.
+ *
+ * It is called when:
+ * - a user requests a read
+ * - after a read request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ *
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueRead
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ // If we're not currently processing a read AND we have an available read stream
+ if ((currentRead == nil) && (flags & kConnected))
+ {
+ if ([readQueue count] > 0)
+ {
+ // Dequeue the next object in the write queue
+ currentRead = [readQueue objectAtIndex:0];
+ [readQueue removeObjectAtIndex:0];
+
+
+ if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
+ {
+ LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+
+ // Attempt to start TLS
+ flags |= kStartingReadTLS;
+
+ // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+ [self maybeStartTLS];
+ }
+ else
+ {
+ LogVerbose(@"Dequeued GCDAsyncReadPacket");
+
+ // Setup read timer (if needed)
+ [self setupReadTimerWithTimeout:currentRead->timeout];
+
+ // Immediately read, if possible
+ [self doReadData];
+ }
+ }
+ else if (flags & kDisconnectAfterReads)
+ {
+ if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else if (flags & kSocketSecure)
+ {
+ [self flushSSLBuffers];
+
+ // Edge case:
+ //
+ // We just drained all data from the ssl buffers,
+ // and all known data from the socket (socketFDBytesAvailable).
+ //
+ // If we didn't get any data from this process,
+ // then we may have reached the end of the TCP stream.
+ //
+ // Be sure callbacks are enabled so we're notified about a disconnection.
+
+ if ([preBuffer availableBytes] == 0)
+ {
+ if ([self usingCFStreamForTLS]) {
+ // Callbacks never disabled
+ }
+ else {
+ [self resumeReadSource];
+ }
+ }
+ }
+ }
+}
+
+- (void)flushSSLBuffers
+{
+ LogTrace();
+
+ NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ // Only flush the ssl buffers if the prebuffer is empty.
+ // This is to avoid growing the prebuffer inifinitely large.
+
+ return;
+ }
+
+ #if TARGET_OS_IPHONE
+
+ if ([self usingCFStreamForTLS])
+ {
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ {
+ LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+
+ CFIndex defaultBytesToRead = (1024 * 4);
+
+ [preBuffer ensureCapacityForWrite:defaultBytesToRead];
+
+ uint8_t *buffer = [preBuffer writeBuffer];
+
+ CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
+ LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
+
+ if (result > 0)
+ {
+ [preBuffer didWrite:result];
+ }
+
+ flags &= ~kSecureSocketHasBytesAvailable;
+ }
+
+ return;
+ }
+
+ #endif
+
+ __block NSUInteger estimatedBytesAvailable = 0;
+
+ dispatch_block_t updateEstimatedBytesAvailable = ^{
+
+ // Figure out if there is any data available to be read
+ //
+ // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
+ // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket
+ // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered
+ //
+ // We call the variable "estimated" because we don't know how many decrypted bytes we'll get
+ // from the encrypted bytes in the sslPreBuffer.
+ // However, we do know this is an upper bound on the estimation.
+
+ estimatedBytesAvailable = socketFDBytesAvailable + [sslPreBuffer availableBytes];
+
+ size_t sslInternalBufSize = 0;
+ SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+
+ estimatedBytesAvailable += sslInternalBufSize;
+ };
+
+ updateEstimatedBytesAvailable();
+
+ if (estimatedBytesAvailable > 0)
+ {
+ LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+
+ BOOL done = NO;
+ do
+ {
+ LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
+
+ // Make sure there's enough room in the prebuffer
+
+ [preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
+
+ // Read data into prebuffer
+
+ uint8_t *buffer = [preBuffer writeBuffer];
+ size_t bytesRead = 0;
+
+ OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
+ LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
+
+ if (bytesRead > 0)
+ {
+ [preBuffer didWrite:bytesRead];
+ }
+
+ LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
+
+ if (result != noErr)
+ {
+ done = YES;
+ }
+ else
+ {
+ updateEstimatedBytesAvailable();
+ }
+
+ } while (!done && estimatedBytesAvailable > 0);
+ }
+}
+
+- (void)doReadData
+{
+ LogTrace();
+
+ // This method is called on the socketQueue.
+ // It might be called directly, or via the readSource when data is available to be read.
+
+ if ((currentRead == nil) || (flags & kReadsPaused))
+ {
+ LogVerbose(@"No currentRead or kReadsPaused");
+
+ // Unable to read at this time
+
+ if (flags & kSocketSecure)
+ {
+ // Here's the situation:
+ //
+ // We have an established secure connection.
+ // There may not be a currentRead, but there might be encrypted data sitting around for us.
+ // When the user does get around to issuing a read, that encrypted data will need to be decrypted.
+ //
+ // So why make the user wait?
+ // We might as well get a head start on decrypting some data now.
+ //
+ // The other reason we do this has to do with detecting a socket disconnection.
+ // The SSL/TLS protocol has it's own disconnection handshake.
+ // So when a secure socket is closed, a "goodbye" packet comes across the wire.
+ // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
+
+ [self flushSSLBuffers];
+ }
+
+ if ([self usingCFStreamForTLS])
+ {
+ // CFReadStream only fires once when there is available data.
+ // It won't fire again until we've invoked CFReadStreamRead.
+ }
+ else
+ {
+ // If the readSource is firing, we need to pause it
+ // or else it will continue to fire over and over again.
+ //
+ // If the readSource is not firing,
+ // we want it to continue monitoring the socket.
+
+ if (socketFDBytesAvailable > 0)
+ {
+ [self suspendReadSource];
+ }
+ }
+ return;
+ }
+
+ BOOL hasBytesAvailable = NO;
+ unsigned long estimatedBytesAvailable = 0;
+
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS)
+
+ estimatedBytesAvailable = 0;
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ hasBytesAvailable = YES;
+ else
+ hasBytesAvailable = NO;
+
+ #endif
+ }
+ else
+ {
+ estimatedBytesAvailable = socketFDBytesAvailable;
+
+ if (flags & kSocketSecure)
+ {
+ // There are 2 buffers to be aware of here.
+ //
+ // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
+ // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
+ // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
+ // SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
+ //
+ // The first buffer is one we create.
+ // SecureTransport often requests small amounts of data.
+ // This has to do with the encypted packets that are coming across the TCP stream.
+ // But it's non-optimal to do a bunch of small reads from the BSD socket.
+ // So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
+ // and may store excess in the sslPreBuffer.
+
+ estimatedBytesAvailable += [sslPreBuffer availableBytes];
+
+ // The second buffer is within SecureTransport.
+ // As mentioned earlier, there are encrypted packets coming across the TCP stream.
+ // SecureTransport needs the entire packet to decrypt it.
+ // But if the entire packet produces X bytes of decrypted data,
+ // and we only asked SecureTransport for X/2 bytes of data,
+ // it must store the extra X/2 bytes of decrypted data for the next read.
+ //
+ // The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
+ // From the documentation:
+ //
+ // "This function does not block or cause any low-level read operations to occur."
+
+ size_t sslInternalBufSize = 0;
+ SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+
+ estimatedBytesAvailable += sslInternalBufSize;
+ }
+
+ hasBytesAvailable = (estimatedBytesAvailable > 0);
+ }
+
+ if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
+ {
+ LogVerbose(@"No data available to read...");
+
+ // No data available to read.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Need to wait for readSource to fire and notify us of
+ // available data in the socket's internal read buffer.
+
+ [self resumeReadSource];
+ }
+ return;
+ }
+
+ if (flags & kStartingReadTLS)
+ {
+ LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+
+ // The readQueue is waiting for SSL/TLS handshake to complete.
+
+ if (flags & kStartingWriteTLS)
+ {
+ if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+ {
+ // We are in the process of a SSL Handshake.
+ // We were waiting for incoming data which has just arrived.
+
+ [self ssl_continueSSLHandshake];
+ }
+ }
+ else
+ {
+ // We are still waiting for the writeQueue to drain and start the SSL/TLS process.
+ // We now know data is available to read.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the read source or else it will continue to fire nonstop.
+
+ [self suspendReadSource];
+ }
+ }
+
+ return;
+ }
+
+ BOOL done = NO; // Completed read operation
+ NSError *error = nil; // Error occurred
+
+ NSUInteger totalBytesReadForCurrentRead = 0;
+
+ //
+ // STEP 1 - READ FROM PREBUFFER
+ //
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ // There are 3 types of read packets:
+ //
+ // 1) Read all available data.
+ // 2) Read a specific length of data.
+ // 3) Read up to a particular terminator.
+
+ NSUInteger bytesToCopy;
+
+ if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+ }
+ else
+ {
+ // Read type #1 or #2
+
+ bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+
+ // Copy bytes from prebuffer into packet buffer
+
+ uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
+ currentRead->bytesDone;
+
+ memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
+
+ // Remove the copied bytes from the preBuffer
+ [preBuffer didRead:bytesToCopy];
+
+ LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
+
+ // Update totals
+
+ currentRead->bytesDone += bytesToCopy;
+ totalBytesReadForCurrentRead += bytesToCopy;
+
+ // Check to see if the read operation is done
+
+ if (currentRead->readLength > 0)
+ {
+ // Read type #2 - read a specific length of data
+
+ done = (currentRead->bytesDone == currentRead->readLength);
+ }
+ else if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
+
+ if (!done && currentRead->maxLength > 0)
+ {
+ // We're not done and there's a set maxLength.
+ // Have we reached that maxLength yet?
+
+ if (currentRead->bytesDone >= currentRead->maxLength)
+ {
+ error = [self readMaxedOutError];
+ }
+ }
+ }
+ else
+ {
+ // Read type #1 - read all available data
+ //
+ // We're done as soon as
+ // - we've read all available data (in prebuffer and socket)
+ // - we've read the maxLength of read packet.
+
+ done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
+ }
+
+ }
+
+ //
+ // STEP 2 - READ FROM SOCKET
+ //
+
+ BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file)
+ BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
+
+ if (!done && !error && !socketEOF && hasBytesAvailable)
+ {
+ NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
+
+ BOOL readIntoPreBuffer = NO;
+ uint8_t *buffer = NULL;
+ size_t bytesRead = 0;
+
+ if (flags & kSocketSecure)
+ {
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ // Using CFStream, rather than SecureTransport, for TLS
+
+ NSUInteger defaultReadLength = (1024 * 32);
+
+ NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+ shouldPreBuffer:&readIntoPreBuffer];
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // Read data into buffer
+
+ CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
+ LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
+
+ if (result < 0)
+ {
+ error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
+ }
+ else if (result == 0)
+ {
+ socketEOF = YES;
+ }
+ else
+ {
+ waiting = YES;
+ bytesRead = (size_t)result;
+ }
+
+ // We only know how many decrypted bytes were read.
+ // The actual number of bytes read was likely more due to the overhead of the encryption.
+ // So we reset our flag, and rely on the next callback to alert us of more data.
+ flags &= ~kSecureSocketHasBytesAvailable;
+
+ #endif
+ }
+ else
+ {
+ // Using SecureTransport for TLS
+ //
+ // We know:
+ // - how many bytes are available on the socket
+ // - how many encrypted bytes are sitting in the sslPreBuffer
+ // - how many decypted bytes are sitting in the sslContext
+ //
+ // But we do NOT know:
+ // - how many encypted bytes are sitting in the sslContext
+ //
+ // So we play the regular game of using an upper bound instead.
+
+ NSUInteger defaultReadLength = (1024 * 32);
+
+ if (defaultReadLength < estimatedBytesAvailable) {
+ defaultReadLength = estimatedBytesAvailable + (1024 * 16);
+ }
+
+ NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+ shouldPreBuffer:&readIntoPreBuffer];
+
+ if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t
+ bytesToRead = SIZE_MAX;
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // The documentation from Apple states:
+ //
+ // "a read operation might return errSSLWouldBlock,
+ // indicating that less data than requested was actually transferred"
+ //
+ // However, starting around 10.7, the function will sometimes return noErr,
+ // even if it didn't read as much data as requested. So we need to watch out for that.
+
+ OSStatus result;
+ do
+ {
+ void *loop_buffer = buffer + bytesRead;
+ size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
+ size_t loop_bytesRead = 0;
+
+ result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead);
+ LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead);
+
+ bytesRead += loop_bytesRead;
+
+ } while ((result == noErr) && (bytesRead < bytesToRead));
+
+
+ if (result != noErr)
+ {
+ if (result == errSSLWouldBlock)
+ waiting = YES;
+ else
+ {
+ if (result == errSSLClosedGraceful || result == errSSLClosedAbort)
+ {
+ // We've reached the end of the stream.
+ // Handle this the same way we would an EOF from the socket.
+ socketEOF = YES;
+ sslErrCode = result;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+ }
+ // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock.
+ // This happens when the SSLRead function is able to read some data,
+ // but not the entire amount we requested.
+
+ if (bytesRead <= 0)
+ {
+ bytesRead = 0;
+ }
+ }
+
+ // Do not modify socketFDBytesAvailable.
+ // It will be updated via the SSLReadFunction().
+ }
+ }
+ else
+ {
+ // Normal socket operation
+
+ NSUInteger bytesToRead;
+
+ // There are 3 types of read packets:
+ //
+ // 1) Read all available data.
+ // 2) Read a specific length of data.
+ // 3) Read up to a particular terminator.
+
+ if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
+ shouldPreBuffer:&readIntoPreBuffer];
+ }
+ else
+ {
+ // Read type #1 or #2
+
+ bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
+ }
+
+ if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
+ bytesToRead = SIZE_MAX;
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // Read data into buffer
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
+ LogVerbose(@"read from socket = %i", (int)result);
+
+ if (result < 0)
+ {
+ if (errno == EWOULDBLOCK)
+ waiting = YES;
+ else
+ error = [self errnoErrorWithReason:@"Error in read() function"];
+
+ socketFDBytesAvailable = 0;
+ }
+ else if (result == 0)
+ {
+ socketEOF = YES;
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ bytesRead = result;
+
+ if (bytesRead < bytesToRead)
+ {
+ // The read returned less data than requested.
+ // This means socketFDBytesAvailable was a bit off due to timing,
+ // because we read from the socket right when the readSource event was firing.
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ if (socketFDBytesAvailable <= bytesRead)
+ socketFDBytesAvailable = 0;
+ else
+ socketFDBytesAvailable -= bytesRead;
+ }
+
+ if (socketFDBytesAvailable == 0)
+ {
+ waiting = YES;
+ }
+ }
+ }
+
+ if (bytesRead > 0)
+ {
+ // Check to see if the read operation is done
+
+ if (currentRead->readLength > 0)
+ {
+ // Read type #2 - read a specific length of data
+ //
+ // Note: We should never be using a prebuffer when we're reading a specific length of data.
+
+ NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+
+ done = (currentRead->bytesDone == currentRead->readLength);
+ }
+ else if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ if (readIntoPreBuffer)
+ {
+ // We just read a big chunk of data into the preBuffer
+
+ [preBuffer didWrite:bytesRead];
+ LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Search for the terminating sequence
+
+ NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+ LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy);
+
+ // Ensure there's room on the read packet's buffer
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+
+ // Copy bytes from prebuffer into read buffer
+
+ uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ + currentRead->bytesDone;
+
+ memcpy(readBuf, [preBuffer readBuffer], bytesToCopy);
+
+ // Remove the copied bytes from the prebuffer
+ [preBuffer didRead:bytesToCopy];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Update totals
+ currentRead->bytesDone += bytesToCopy;
+ totalBytesReadForCurrentRead += bytesToCopy;
+
+ // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
+ }
+ else
+ {
+ // We just read a big chunk of data directly into the packet's buffer.
+ // We need to move any overflow into the prebuffer.
+
+ NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
+
+ if (overflow == 0)
+ {
+ // Perfect match!
+ // Every byte we read stays in the read buffer,
+ // and the last byte we read was the last byte of the term.
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ done = YES;
+ }
+ else if (overflow > 0)
+ {
+ // The term was found within the data that we read,
+ // and there are extra bytes that extend past the end of the term.
+ // We need to move these excess bytes out of the read packet and into the prebuffer.
+
+ NSInteger underflow = bytesRead - overflow;
+
+ // Copy excess data into preBuffer
+
+ LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow);
+ [preBuffer ensureCapacityForWrite:overflow];
+
+ uint8_t *overflowBuffer = buffer + underflow;
+ memcpy([preBuffer writeBuffer], overflowBuffer, overflow);
+
+ [preBuffer didWrite:overflow];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Note: The completeCurrentRead method will trim the buffer for us.
+
+ currentRead->bytesDone += underflow;
+ totalBytesReadForCurrentRead += underflow;
+ done = YES;
+ }
+ else
+ {
+ // The term was not found within the data that we read.
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ done = NO;
+ }
+ }
+
+ if (!done && currentRead->maxLength > 0)
+ {
+ // We're not done and there's a set maxLength.
+ // Have we reached that maxLength yet?
+
+ if (currentRead->bytesDone >= currentRead->maxLength)
+ {
+ error = [self readMaxedOutError];
+ }
+ }
+ }
+ else
+ {
+ // Read type #1 - read all available data
+
+ if (readIntoPreBuffer)
+ {
+ // We just read a chunk of data into the preBuffer
+
+ [preBuffer didWrite:bytesRead];
+
+ // Now copy the data into the read packet.
+ //
+ // Recall that we didn't read directly into the packet's buffer to avoid
+ // over-allocating memory since we had no clue how much data was available to be read.
+ //
+ // Ensure there's room on the read packet's buffer
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
+
+ // Copy bytes from prebuffer into read buffer
+
+ uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ + currentRead->bytesDone;
+
+ memcpy(readBuf, [preBuffer readBuffer], bytesRead);
+
+ // Remove the copied bytes from the prebuffer
+ [preBuffer didRead:bytesRead];
+
+ // Update totals
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ }
+ else
+ {
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ }
+
+ done = YES;
+ }
+
+ } // if (bytesRead > 0)
+
+ } // if (!done && !error && !socketEOF && hasBytesAvailable)
+
+
+ if (!done && currentRead->readLength == 0 && currentRead->term == nil)
+ {
+ // Read type #1 - read all available data
+ //
+ // We might arrive here if we read data from the prebuffer but not from the socket.
+
+ done = (totalBytesReadForCurrentRead > 0);
+ }
+
+ // Check to see if we're done, or if we've made progress
+
+ if (done)
+ {
+ [self completeCurrentRead];
+
+ if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
+ {
+ [self maybeDequeueRead];
+ }
+ }
+ else if (totalBytesReadForCurrentRead > 0)
+ {
+ // We're not done read type #2 or #3 yet, but we have read in some bytes
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
+ {
+ long theReadTag = currentRead->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
+ }});
+ }
+ }
+
+ // Check for errors
+
+ if (error)
+ {
+ [self closeWithError:error];
+ }
+ else if (socketEOF)
+ {
+ [self doReadEOF];
+ }
+ else if (waiting)
+ {
+ if (![self usingCFStreamForTLS])
+ {
+ // Monitor the socket for readability (if we're not already doing so)
+ [self resumeReadSource];
+ }
+ }
+
+ // Do not add any code here without first adding return statements in the error cases above.
+}
+
+- (void)doReadEOF
+{
+ LogTrace();
+
+ // This method may be called more than once.
+ // If the EOF is read while there is still data in the preBuffer,
+ // then this method may be called continually after invocations of doReadData to see if it's time to disconnect.
+
+ flags |= kSocketHasReadEOF;
+
+ if (flags & kSocketSecure)
+ {
+ // If the SSL layer has any buffered data, flush it into the preBuffer now.
+
+ [self flushSSLBuffers];
+ }
+
+ BOOL shouldDisconnect = NO;
+ NSError *error = nil;
+
+ if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
+ {
+ // We received an EOF during or prior to startTLS.
+ // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation.
+
+ shouldDisconnect = YES;
+
+ if ([self usingSecureTransportForTLS])
+ {
+ error = [self sslError:errSSLClosedAbort];
+ }
+ }
+ else if (flags & kReadStreamClosed)
+ {
+ // The preBuffer has already been drained.
+ // The config allows half-duplex connections.
+ // We've previously checked the socket, and it appeared writeable.
+ // So we marked the read stream as closed and notified the delegate.
+ //
+ // As per the half-duplex contract, the socket will be closed when a write fails,
+ // or when the socket is manually closed.
+
+ shouldDisconnect = NO;
+ }
+ else if ([preBuffer availableBytes] > 0)
+ {
+ LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
+
+ // Although we won't be able to read any more data from the socket,
+ // there is existing data that has been prebuffered that we can read.
+
+ shouldDisconnect = NO;
+ }
+ else if (config & kAllowHalfDuplexConnection)
+ {
+ // We just received an EOF (end of file) from the socket's read stream.
+ // This means the remote end of the socket (the peer we're connected to)
+ // has explicitly stated that it will not be sending us any more data.
+ //
+ // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ struct pollfd pfd[1];
+ pfd[0].fd = socketFD;
+ pfd[0].events = POLLOUT;
+ pfd[0].revents = 0;
+
+ poll(pfd, 1, 0);
+
+ if (pfd[0].revents & POLLOUT)
+ {
+ // Socket appears to still be writeable
+
+ shouldDisconnect = NO;
+ flags |= kReadStreamClosed;
+
+ // Notify the delegate that we're going half-duplex
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidCloseReadStream:self];
+ }});
+ }
+ }
+ else
+ {
+ shouldDisconnect = YES;
+ }
+ }
+ else
+ {
+ shouldDisconnect = YES;
+ }
+
+
+ if (shouldDisconnect)
+ {
+ if (error == nil)
+ {
+ if ([self usingSecureTransportForTLS])
+ {
+ if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
+ {
+ error = [self sslError:sslErrCode];
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
+ }
+ [self closeWithError:error];
+ }
+ else
+ {
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the read source (if needed)
+
+ [self suspendReadSource];
+ }
+ }
+}
+
+- (void)completeCurrentRead
+{
+ LogTrace();
+
+ NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
+
+
+ NSData *result = nil;
+
+ if (currentRead->bufferOwner)
+ {
+ // We created the buffer on behalf of the user.
+ // Trim our buffer to be the proper size.
+ [currentRead->buffer setLength:currentRead->bytesDone];
+
+ result = currentRead->buffer;
+ }
+ else
+ {
+ // We did NOT create the buffer.
+ // The buffer is owned by the caller.
+ // Only trim the buffer if we had to increase its size.
+
+ if ([currentRead->buffer length] > currentRead->originalBufferLength)
+ {
+ NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
+ NSUInteger origSize = currentRead->originalBufferLength;
+
+ NSUInteger buffSize = MAX(readSize, origSize);
+
+ [currentRead->buffer setLength:buffSize];
+ }
+
+ uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
+
+ result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
+ }
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+ {
+ GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReadData:result withTag:theRead->tag];
+ }});
+ }
+
+ [self endCurrentRead];
+}
+
+- (void)endCurrentRead
+{
+ if (readTimer)
+ {
+ dispatch_source_cancel(readTimer);
+ readTimer = NULL;
+ }
+
+ currentRead = nil;
+}
+
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doReadTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theReadTimer = readTimer;
+ dispatch_source_set_cancel_handler(readTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(readTimer)");
+ dispatch_release(theReadTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+
+ dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+ dispatch_resume(readTimer);
+ }
+}
+
+- (void)doReadTimeout
+{
+ // This is a little bit tricky.
+ // Ideally we'd like to synchronously query the delegate about a timeout extension.
+ // But if we do so synchronously we risk a possible deadlock.
+ // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+
+ flags |= kReadsPaused;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
+ {
+ GCDAsyncReadPacket *theRead = currentRead;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ NSTimeInterval timeoutExtension = 0.0;
+
+ timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
+ elapsed:theRead->timeout
+ bytesDone:theRead->bytesDone];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self doReadTimeoutWithExtension:timeoutExtension];
+ }});
+ }});
+ }
+ else
+ {
+ [self doReadTimeoutWithExtension:0.0];
+ }
+}
+
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+ if (currentRead)
+ {
+ if (timeoutExtension > 0.0)
+ {
+ currentRead->timeout += timeoutExtension;
+
+ // Reschedule the timer
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+ dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ // Unpause reads, and continue
+ flags &= ~kReadsPaused;
+ [self doReadData];
+ }
+ else
+ {
+ LogVerbose(@"ReadTimeout");
+
+ [self closeWithError:[self readTimeoutError]];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Writing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if ([data length] == 0) return;
+
+ GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+ {
+ [writeQueue addObject:packet];
+ [self maybeDequeueWrite];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+ __block float result = 0.0F;
+
+ dispatch_block_t block = ^{
+
+ if (!currentWrite || ![currentWrite isKindOfClass:[GCDAsyncWritePacket class]])
+ {
+ // We're not writing anything right now.
+
+ if (tagPtr != NULL) *tagPtr = 0;
+ if (donePtr != NULL) *donePtr = 0;
+ if (totalPtr != NULL) *totalPtr = 0;
+
+ result = NAN;
+ }
+ else
+ {
+ NSUInteger done = currentWrite->bytesDone;
+ NSUInteger total = [currentWrite->buffer length];
+
+ if (tagPtr != NULL) *tagPtr = currentWrite->tag;
+ if (donePtr != NULL) *donePtr = done;
+ if (totalPtr != NULL) *totalPtr = total;
+
+ result = (float)done / (float)total;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+/**
+ * Conditionally starts a new write.
+ *
+ * It is called when:
+ * - a user requests a write
+ * - after a write request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ *
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueWrite
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ // If we're not currently processing a write AND we have an available write stream
+ if ((currentWrite == nil) && (flags & kConnected))
+ {
+ if ([writeQueue count] > 0)
+ {
+ // Dequeue the next object in the write queue
+ currentWrite = [writeQueue objectAtIndex:0];
+ [writeQueue removeObjectAtIndex:0];
+
+
+ if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
+ {
+ LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+
+ // Attempt to start TLS
+ flags |= kStartingWriteTLS;
+
+ // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+ [self maybeStartTLS];
+ }
+ else
+ {
+ LogVerbose(@"Dequeued GCDAsyncWritePacket");
+
+ // Setup write timer (if needed)
+ [self setupWriteTimerWithTimeout:currentWrite->timeout];
+
+ // Immediately write, if possible
+ [self doWriteData];
+ }
+ }
+ else if (flags & kDisconnectAfterWrites)
+ {
+ if (flags & kDisconnectAfterReads)
+ {
+ if (([readQueue count] == 0) && (currentRead == nil))
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else
+ {
+ [self closeWithError:nil];
+ }
+ }
+ }
+}
+
+- (void)doWriteData
+{
+ LogTrace();
+
+ // This method is called by the writeSource via the socketQueue
+
+ if ((currentWrite == nil) || (flags & kWritesPaused))
+ {
+ LogVerbose(@"No currentWrite or kWritesPaused");
+
+ // Unable to write at this time
+
+ if ([self usingCFStreamForTLS])
+ {
+ // CFWriteStream only fires once when there is available data.
+ // It won't fire again until we've invoked CFWriteStreamWrite.
+ }
+ else
+ {
+ // If the writeSource is firing, we need to pause it
+ // or else it will continue to fire over and over again.
+
+ if (flags & kSocketCanAcceptBytes)
+ {
+ [self suspendWriteSource];
+ }
+ }
+ return;
+ }
+
+ if (!(flags & kSocketCanAcceptBytes))
+ {
+ LogVerbose(@"No space available to write...");
+
+ // No space available to write.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Need to wait for writeSource to fire and notify us of
+ // available space in the socket's internal write buffer.
+
+ [self resumeWriteSource];
+ }
+ return;
+ }
+
+ if (flags & kStartingWriteTLS)
+ {
+ LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+
+ // The writeQueue is waiting for SSL/TLS handshake to complete.
+
+ if (flags & kStartingReadTLS)
+ {
+ if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+ {
+ // We are in the process of a SSL Handshake.
+ // We were waiting for available space in the socket's internal OS buffer to continue writing.
+
+ [self ssl_continueSSLHandshake];
+ }
+ }
+ else
+ {
+ // We are still waiting for the readQueue to drain and start the SSL/TLS process.
+ // We now know we can write to the socket.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the write source or else it will continue to fire nonstop.
+
+ [self suspendWriteSource];
+ }
+ }
+
+ return;
+ }
+
+ // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
+
+ BOOL waiting = NO;
+ NSError *error = nil;
+ size_t bytesWritten = 0;
+
+ if (flags & kSocketSecure)
+ {
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ //
+ // Writing data using CFStream (over internal TLS)
+ //
+
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
+ LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
+
+ if (result < 0)
+ {
+ error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
+ }
+ else
+ {
+ bytesWritten = (size_t)result;
+
+ // We always set waiting to true in this scenario.
+ // CFStream may have altered our underlying socket to non-blocking.
+ // Thus if we attempt to write without a callback, we may end up blocking our queue.
+ waiting = YES;
+ }
+
+ #endif
+ }
+ else
+ {
+ // We're going to use the SSLWrite function.
+ //
+ // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed)
+ //
+ // Parameters:
+ // context - An SSL session context reference.
+ // data - A pointer to the buffer of data to write.
+ // dataLength - The amount, in bytes, of data to write.
+ // processed - On return, the length, in bytes, of the data actually written.
+ //
+ // It sounds pretty straight-forward,
+ // but there are a few caveats you should be aware of.
+ //
+ // The SSLWrite method operates in a non-obvious (and rather annoying) manner.
+ // According to the documentation:
+ //
+ // Because you may configure the underlying connection to operate in a non-blocking manner,
+ // a write operation might return errSSLWouldBlock, indicating that less data than requested
+ // was actually transferred. In this case, you should repeat the call to SSLWrite until some
+ // other result is returned.
+ //
+ // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
+ // then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
+ // but it sets processed to dataLength !!
+ //
+ // In other words, if the SSLWrite function doesn't completely write all the data we tell it to,
+ // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to
+ // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written.
+ //
+ // You might be wondering:
+ // If the SSLWrite function doesn't tell us how many bytes were written,
+ // then how in the world are we supposed to update our parameters (buffer & bytesToWrite)
+ // for the next time we invoke SSLWrite?
+ //
+ // The answer is that SSLWrite cached all the data we told it to write,
+ // and it will push out that data next time we call SSLWrite.
+ // If we call SSLWrite with new data, it will push out the cached data first, and then the new data.
+ // If we call SSLWrite with empty data, then it will simply push out the cached data.
+ //
+ // For this purpose we're going to break large writes into a series of smaller writes.
+ // This allows us to report progress back to the delegate.
+
+ OSStatus result;
+
+ BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
+ BOOL hasNewDataToWrite = YES;
+
+ if (hasCachedDataToWrite)
+ {
+ size_t processed = 0;
+
+ result = SSLWrite(sslContext, NULL, 0, &processed);
+
+ if (result == noErr)
+ {
+ bytesWritten = sslWriteCachedLength;
+ sslWriteCachedLength = 0;
+
+ if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
+ {
+ // We've written all data for the current write.
+ hasNewDataToWrite = NO;
+ }
+ }
+ else
+ {
+ if (result == errSSLWouldBlock)
+ {
+ waiting = YES;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+
+ // Can't write any new data since we were unable to write the cached data.
+ hasNewDataToWrite = NO;
+ }
+ }
+
+ if (hasNewDataToWrite)
+ {
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+ + currentWrite->bytesDone
+ + bytesWritten;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ size_t bytesRemaining = bytesToWrite;
+
+ BOOL keepLooping = YES;
+ while (keepLooping)
+ {
+ const size_t sslMaxBytesToWrite = 32768;
+ size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
+ size_t sslBytesWritten = 0;
+
+ result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
+
+ if (result == noErr)
+ {
+ buffer += sslBytesWritten;
+ bytesWritten += sslBytesWritten;
+ bytesRemaining -= sslBytesWritten;
+
+ keepLooping = (bytesRemaining > 0);
+ }
+ else
+ {
+ if (result == errSSLWouldBlock)
+ {
+ waiting = YES;
+ sslWriteCachedLength = sslBytesToWrite;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+
+ keepLooping = NO;
+ }
+
+ } // while (keepLooping)
+
+ } // if (hasNewDataToWrite)
+ }
+ }
+ else
+ {
+ //
+ // Writing data directly over raw socket
+ //
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
+ LogVerbose(@"wrote to socket = %zd", result);
+
+ // Check results
+ if (result < 0)
+ {
+ if (errno == EWOULDBLOCK)
+ {
+ waiting = YES;
+ }
+ else
+ {
+ error = [self errnoErrorWithReason:@"Error in write() function"];
+ }
+ }
+ else
+ {
+ bytesWritten = result;
+ }
+ }
+
+ // We're done with our writing.
+ // If we explictly ran into a situation where the socket told us there was no room in the buffer,
+ // then we immediately resume listening for notifications.
+ //
+ // We must do this before we dequeue another write,
+ // as that may in turn invoke this method again.
+ //
+ // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode.
+
+ if (waiting)
+ {
+ flags &= ~kSocketCanAcceptBytes;
+
+ if (![self usingCFStreamForTLS])
+ {
+ [self resumeWriteSource];
+ }
+ }
+
+ // Check our results
+
+ BOOL done = NO;
+
+ if (bytesWritten > 0)
+ {
+ // Update total amount read for the current write
+ currentWrite->bytesDone += bytesWritten;
+ LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone);
+
+ // Is packet done?
+ done = (currentWrite->bytesDone == [currentWrite->buffer length]);
+ }
+
+ if (done)
+ {
+ [self completeCurrentWrite];
+
+ if (!error)
+ {
+ dispatch_async(socketQueue, ^{ @autoreleasepool{
+
+ [self maybeDequeueWrite];
+ }});
+ }
+ }
+ else
+ {
+ // We were unable to finish writing the data,
+ // so we're waiting for another callback to notify us of available space in the lower-level output buffer.
+
+ if (!waiting && !error)
+ {
+ // This would be the case if our write was able to accept some data, but not all of it.
+
+ flags &= ~kSocketCanAcceptBytes;
+
+ if (![self usingCFStreamForTLS])
+ {
+ [self resumeWriteSource];
+ }
+ }
+
+ if (bytesWritten > 0)
+ {
+ // We're not done with the entire write, but we have written some bytes
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
+ {
+ long theWriteTag = currentWrite->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
+ }});
+ }
+ }
+ }
+
+ // Check for errors
+
+ if (error)
+ {
+ [self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]];
+ }
+
+ // Do not add any code here without first adding a return statement in the error case above.
+}
+
+- (void)completeCurrentWrite
+{
+ LogTrace();
+
+ NSAssert(currentWrite, @"Trying to complete current write when there is no current write.");
+
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
+ {
+ long theWriteTag = currentWrite->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didWriteDataWithTag:theWriteTag];
+ }});
+ }
+
+ [self endCurrentWrite];
+}
+
+- (void)endCurrentWrite
+{
+ if (writeTimer)
+ {
+ dispatch_source_cancel(writeTimer);
+ writeTimer = NULL;
+ }
+
+ currentWrite = nil;
+}
+
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doWriteTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theWriteTimer = writeTimer;
+ dispatch_source_set_cancel_handler(writeTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(writeTimer)");
+ dispatch_release(theWriteTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+
+ dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+ dispatch_resume(writeTimer);
+ }
+}
+
+- (void)doWriteTimeout
+{
+ // This is a little bit tricky.
+ // Ideally we'd like to synchronously query the delegate about a timeout extension.
+ // But if we do so synchronously we risk a possible deadlock.
+ // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+
+ flags |= kWritesPaused;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
+ {
+ GCDAsyncWritePacket *theWrite = currentWrite;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ NSTimeInterval timeoutExtension = 0.0;
+
+ timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
+ elapsed:theWrite->timeout
+ bytesDone:theWrite->bytesDone];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self doWriteTimeoutWithExtension:timeoutExtension];
+ }});
+ }});
+ }
+ else
+ {
+ [self doWriteTimeoutWithExtension:0.0];
+ }
+}
+
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+ if (currentWrite)
+ {
+ if (timeoutExtension > 0.0)
+ {
+ currentWrite->timeout += timeoutExtension;
+
+ // Reschedule the timer
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+ dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ // Unpause writes, and continue
+ flags &= ~kWritesPaused;
+ [self doWriteData];
+ }
+ else
+ {
+ LogVerbose(@"WriteTimeout");
+
+ [self closeWithError:[self writeTimeoutError]];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)startTLS:(NSDictionary *)tlsSettings
+{
+ LogTrace();
+
+ if (tlsSettings == nil)
+ {
+ // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
+ // but causes problems if we later try to fetch the remote host's certificate.
+ //
+ // To be exact, it causes the following to return NULL instead of the normal result:
+ // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
+ //
+ // So we use an empty dictionary instead, which works perfectly.
+
+ tlsSettings = [NSDictionary dictionary];
+ }
+
+ GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites))
+ {
+ [readQueue addObject:packet];
+ [writeQueue addObject:packet];
+
+ flags |= kQueuedTLS;
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+ }});
+
+}
+
+- (void)maybeStartTLS
+{
+ // We can't start TLS until:
+ // - All queued reads prior to the user calling startTLS are complete
+ // - All queued writes prior to the user calling startTLS are complete
+ //
+ // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ BOOL useSecureTransport = YES;
+
+ #if TARGET_OS_IPHONE
+ {
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+
+ NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS];
+ if (value && [value boolValue])
+ useSecureTransport = NO;
+ }
+ #endif
+
+ if (useSecureTransport)
+ {
+ [self ssl_startTLS];
+ }
+ else
+ {
+ #if TARGET_OS_IPHONE
+ [self cf_startTLS];
+ #endif
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via SecureTransport
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
+{
+ LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
+
+ if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0))
+ {
+ LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
+
+ // No data available to read.
+ //
+ // Need to wait for readSource to fire and notify us of
+ // available data in the socket's internal read buffer.
+
+ [self resumeReadSource];
+
+ *bufferLength = 0;
+ return errSSLWouldBlock;
+ }
+
+ size_t totalBytesRead = 0;
+ size_t totalBytesLeftToBeRead = *bufferLength;
+
+ BOOL done = NO;
+ BOOL socketError = NO;
+
+ //
+ // STEP 1 : READ FROM SSL PRE BUFFER
+ //
+
+ size_t sslPreBufferLength = [sslPreBuffer availableBytes];
+
+ if (sslPreBufferLength > 0)
+ {
+ LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
+
+ size_t bytesToCopy;
+ if (sslPreBufferLength > totalBytesLeftToBeRead)
+ bytesToCopy = totalBytesLeftToBeRead;
+ else
+ bytesToCopy = sslPreBufferLength;
+
+ LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy);
+
+ memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
+
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+
+ totalBytesRead += bytesToCopy;
+ totalBytesLeftToBeRead -= bytesToCopy;
+
+ done = (totalBytesLeftToBeRead == 0);
+
+ if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+ }
+
+ //
+ // STEP 2 : READ FROM SOCKET
+ //
+
+ if (!done && (socketFDBytesAvailable > 0))
+ {
+ LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ BOOL readIntoPreBuffer;
+ size_t bytesToRead;
+ uint8_t *buf;
+
+ if (socketFDBytesAvailable > totalBytesLeftToBeRead)
+ {
+ // Read all available data from socket into sslPreBuffer.
+ // Then copy requested amount into dataBuffer.
+
+ LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);
+
+ [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];
+
+ readIntoPreBuffer = YES;
+ bytesToRead = (size_t)socketFDBytesAvailable;
+ buf = [sslPreBuffer writeBuffer];
+ }
+ else
+ {
+ // Read available data from socket directly into dataBuffer.
+
+ LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);
+
+ readIntoPreBuffer = NO;
+ bytesToRead = totalBytesLeftToBeRead;
+ buf = (uint8_t *)buffer + totalBytesRead;
+ }
+
+ ssize_t result = read(socketFD, buf, bytesToRead);
+ LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);
+
+ if (result < 0)
+ {
+ LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);
+
+ if (errno != EWOULDBLOCK)
+ {
+ socketError = YES;
+ }
+
+ socketFDBytesAvailable = 0;
+ }
+ else if (result == 0)
+ {
+ LogVerbose(@"%@: read EOF", THIS_METHOD);
+
+ socketError = YES;
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ size_t bytesReadFromSocket = result;
+
+ if (socketFDBytesAvailable > bytesReadFromSocket)
+ socketFDBytesAvailable -= bytesReadFromSocket;
+ else
+ socketFDBytesAvailable = 0;
+
+ if (readIntoPreBuffer)
+ {
+ [sslPreBuffer didWrite:bytesReadFromSocket];
+
+ size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
+
+ LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy);
+
+ memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
+
+ totalBytesRead += bytesToCopy;
+ totalBytesLeftToBeRead -= bytesToCopy;
+
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+ }
+ else
+ {
+ totalBytesRead += bytesReadFromSocket;
+ totalBytesLeftToBeRead -= bytesReadFromSocket;
+ }
+
+ done = (totalBytesLeftToBeRead == 0);
+
+ if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+ }
+ }
+
+ *bufferLength = totalBytesRead;
+
+ if (done)
+ return noErr;
+
+ if (socketError)
+ return errSSLClosedAbort;
+
+ return errSSLWouldBlock;
+}
+
+- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
+{
+ if (!(flags & kSocketCanAcceptBytes))
+ {
+ // Unable to write.
+ //
+ // Need to wait for writeSource to fire and notify us of
+ // available space in the socket's internal write buffer.
+
+ [self resumeWriteSource];
+
+ *bufferLength = 0;
+ return errSSLWouldBlock;
+ }
+
+ size_t bytesToWrite = *bufferLength;
+ size_t bytesWritten = 0;
+
+ BOOL done = NO;
+ BOOL socketError = NO;
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ ssize_t result = write(socketFD, buffer, bytesToWrite);
+
+ if (result < 0)
+ {
+ if (errno != EWOULDBLOCK)
+ {
+ socketError = YES;
+ }
+
+ flags &= ~kSocketCanAcceptBytes;
+ }
+ else if (result == 0)
+ {
+ flags &= ~kSocketCanAcceptBytes;
+ }
+ else
+ {
+ bytesWritten = result;
+
+ done = (bytesWritten == bytesToWrite);
+ }
+
+ *bufferLength = bytesWritten;
+
+ if (done)
+ return noErr;
+
+ if (socketError)
+ return errSSLClosedAbort;
+
+ return errSSLWouldBlock;
+}
+
+static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+
+ NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+
+ return [asyncSocket sslReadWithBuffer:data length:dataLength];
+}
+
+static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+
+ NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+
+ return [asyncSocket sslWriteWithBuffer:data length:dataLength];
+}
+
+- (void)ssl_startTLS
+{
+ LogTrace();
+
+ LogVerbose(@"Starting TLS (via SecureTransport)...");
+
+ OSStatus status;
+
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ if (tlsPacket == nil) // Code to quiet the analyzer
+ {
+ NSAssert(NO, @"Logic error");
+
+ [self closeWithError:[self otherError:@"Logic error"]];
+ return;
+ }
+ NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+
+ // Create SSLContext, and setup IO callbacks and connection ref
+
+ BOOL isServer = [[tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer] boolValue];
+
+ #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+ {
+ if (isServer)
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
+ else
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
+
+ if (sslContext == NULL)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLCreateContext"]];
+ return;
+ }
+ }
+ #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+ {
+ status = SSLNewContext(isServer, &sslContext);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+ return;
+ }
+ }
+ #endif
+
+ status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+ return;
+ }
+
+ status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+ return;
+ }
+
+
+ BOOL shouldManuallyEvaluateTrust = [[tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust] boolValue];
+ if (shouldManuallyEvaluateTrust)
+ {
+ if (isServer)
+ {
+ [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]];
+ return;
+ }
+
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]];
+ return;
+ }
+
+ #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+
+ // Note from Apple's documentation:
+ //
+ // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8.
+ // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the
+ // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus
+ // SSLSetEnableCertVerify is not available on that platform at all.
+
+ status = SSLSetEnableCertVerify(sslContext, NO);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
+ return;
+ }
+
+ #endif
+ }
+
+ // Configure SSLContext from given settings
+ //
+ // Checklist:
+ // 1. kCFStreamSSLPeerName
+ // 2. kCFStreamSSLCertificates
+ // 3. GCDAsyncSocketSSLPeerID
+ // 4. GCDAsyncSocketSSLProtocolVersionMin
+ // 5. GCDAsyncSocketSSLProtocolVersionMax
+ // 6. GCDAsyncSocketSSLSessionOptionFalseStart
+ // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+ // 8. GCDAsyncSocketSSLCipherSuites
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
+ //
+ // Deprecated (throw error):
+ // 10. kCFStreamSSLAllowsAnyRoot
+ // 11. kCFStreamSSLAllowsExpiredRoots
+ // 12. kCFStreamSSLAllowsExpiredCertificates
+ // 13. kCFStreamSSLValidatesCertificateChain
+ // 14. kCFStreamSSLLevel
+
+ id value;
+
+ // 1. kCFStreamSSLPeerName
+
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName];
+ if ([value isKindOfClass:[NSString class]])
+ {
+ NSString *peerName = (NSString *)value;
+
+ const char *peer = [peerName UTF8String];
+ size_t peerLen = strlen(peer);
+
+ status = SSLSetPeerDomainName(sslContext, peer, peerLen);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString.");
+
+ [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]];
+ return;
+ }
+
+ // 2. kCFStreamSSLCertificates
+
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates];
+ if ([value isKindOfClass:[NSArray class]])
+ {
+ CFArrayRef certs = (__bridge CFArrayRef)value;
+
+ status = SSLSetCertificate(sslContext, certs);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray.");
+
+ [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]];
+ return;
+ }
+
+ // 3. GCDAsyncSocketSSLPeerID
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID];
+ if ([value isKindOfClass:[NSData class]])
+ {
+ NSData *peerIdData = (NSData *)value;
+
+ status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData."
+ @" (You can convert strings to data using a method like"
+ @" [string dataUsingEncoding:NSUTF8StringEncoding])");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]];
+ return;
+ }
+
+ // 4. GCDAsyncSocketSSLProtocolVersionMin
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+ if (minProtocol != kSSLProtocolUnknown)
+ {
+ status = SSLSetProtocolVersionMin(sslContext, minProtocol);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]];
+ return;
+ }
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]];
+ return;
+ }
+
+ // 5. GCDAsyncSocketSSLProtocolVersionMax
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+ if (maxProtocol != kSSLProtocolUnknown)
+ {
+ status = SSLSetProtocolVersionMax(sslContext, maxProtocol);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]];
+ return;
+ }
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]];
+ return;
+ }
+
+ // 6. GCDAsyncSocketSSLSessionOptionFalseStart
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [value boolValue]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]];
+ return;
+ }
+
+ // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [value boolValue]);
+ if (status != noErr)
+ {
+ [self closeWithError:
+ [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."
+ @" Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]];
+ return;
+ }
+
+ // 8. GCDAsyncSocketSSLCipherSuites
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+ if ([value isKindOfClass:[NSArray class]])
+ {
+ NSArray *cipherSuites = (NSArray *)value;
+ NSUInteger numberCiphers = [cipherSuites count];
+ SSLCipherSuite ciphers[numberCiphers];
+
+ NSUInteger cipherIndex;
+ for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
+ {
+ NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
+ ciphers[cipherIndex] = [cipherObject shortValue];
+ }
+
+ status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]];
+ return;
+ }
+
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters
+
+ #if !TARGET_OS_IPHONE
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+ if ([value isKindOfClass:[NSData class]])
+ {
+ NSData *diffieHellmanData = (NSData *)value;
+
+ status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]];
+ return;
+ }
+ #endif
+
+ // DEPRECATED checks
+
+ // 10. kCFStreamSSLAllowsAnyRoot
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]];
+ return;
+ }
+
+ // 11. kCFStreamSSLAllowsExpiredRoots
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]];
+ return;
+ }
+
+ // 12. kCFStreamSSLValidatesCertificateChain
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]];
+ return;
+ }
+
+ // 13. kCFStreamSSLAllowsExpiredCertificates
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]];
+ return;
+ }
+
+ // 14. kCFStreamSSLLevel
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel"
+ @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]];
+ return;
+ }
+
+ // Setup the sslPreBuffer
+ //
+ // Any data in the preBuffer needs to be moved into the sslPreBuffer,
+ // as this data is now part of the secure read stream.
+
+ sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+
+ size_t preBufferLength = [preBuffer availableBytes];
+
+ if (preBufferLength > 0)
+ {
+ [sslPreBuffer ensureCapacityForWrite:preBufferLength];
+
+ memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
+ [preBuffer didRead:preBufferLength];
+ [sslPreBuffer didWrite:preBufferLength];
+ }
+
+ sslErrCode = lastSSLHandshakeError = noErr;
+
+ // Start the SSL Handshake process
+
+ [self ssl_continueSSLHandshake];
+}
+
+- (void)ssl_continueSSLHandshake
+{
+ LogTrace();
+
+ // If the return value is noErr, the session is ready for normal secure communication.
+ // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again.
+ // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the
+ // server and then call SSLHandshake again to resume the handshake or close the connection
+ // errSSLPeerBadCert SSL error.
+ // Otherwise, the return value indicates an error code.
+
+ OSStatus status = SSLHandshake(sslContext);
+ lastSSLHandshakeError = status;
+
+ if (status == noErr)
+ {
+ LogVerbose(@"SSLHandshake complete");
+
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ flags |= kSocketSecure;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidSecure:self];
+ }});
+ }
+
+ [self endCurrentRead];
+ [self endCurrentWrite];
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+ else if (status == errSSLPeerAuthCompleted)
+ {
+ LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval");
+
+ __block SecTrustRef trust = NULL;
+ status = SSLCopyPeerTrust(sslContext, &trust);
+ if (status != noErr)
+ {
+ [self closeWithError:[self sslError:status]];
+ return;
+ }
+
+ int aStateIndex = stateIndex;
+ dispatch_queue_t theSocketQueue = socketQueue;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ dispatch_async(theSocketQueue, ^{ @autoreleasepool {
+
+ if (trust) {
+ CFRelease(trust);
+ trust = NULL;
+ }
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf)
+ {
+ [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex];
+ }
+ }});
+
+ #pragma clang diagnostic pop
+ }};
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler];
+ }});
+ }
+ else
+ {
+ if (trust) {
+ CFRelease(trust);
+ trust = NULL;
+ }
+
+ NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings,"
+ @" but delegate doesn't implement socket:shouldTrustPeer:";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+ }
+ else if (status == errSSLWouldBlock)
+ {
+ LogVerbose(@"SSLHandshake continues...");
+
+ // Handshake continues...
+ //
+ // This method will be called again from doReadData or doWriteData.
+ }
+ else
+ {
+ [self closeWithError:[self sslError:status]];
+ }
+}
+
+- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex
+{
+ LogTrace();
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)");
+
+ // One of the following is true
+ // - the socket was disconnected
+ // - the startTLS operation timed out
+ // - the completionHandler was already invoked once
+
+ return;
+ }
+
+ // Increment stateIndex to ensure completionHandler can only be called once.
+ stateIndex++;
+
+ if (shouldTrust)
+ {
+ NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError);
+ [self ssl_continueSSLHandshake];
+ }
+ else
+ {
+ [self closeWithError:[self sslError:errSSLPeerBadCert]];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+- (void)cf_finishSSLHandshake
+{
+ LogTrace();
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ flags |= kSocketSecure;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidSecure:self];
+ }});
+ }
+
+ [self endCurrentRead];
+ [self endCurrentWrite];
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+}
+
+- (void)cf_abortSSLHandshake:(NSError *)error
+{
+ LogTrace();
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ [self closeWithError:error];
+ }
+}
+
+- (void)cf_startTLS
+{
+ LogTrace();
+
+ LogVerbose(@"Starting TLS (via CFStream)...");
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ [self suspendReadSource];
+ [self suspendWriteSource];
+
+ socketFDBytesAvailable = 0;
+ flags &= ~kSocketCanAcceptBytes;
+ flags &= ~kSecureSocketHasBytesAvailable;
+
+ flags |= kUsingCFStreamForTLS;
+
+ if (![self createReadAndWriteStream])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]];
+ return;
+ }
+
+ if (![self registerForStreamCallbacksIncludingReadWrite:YES])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+ return;
+ }
+
+ if (![self addStreamsToRunLoop])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+ return;
+ }
+
+ NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS");
+ NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS");
+
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings;
+
+ // Getting an error concerning kCFStreamPropertySSLSettings ?
+ // You need to add the CFNetwork framework to your iOS application.
+
+ BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings);
+ BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings);
+
+ // For some reason, starting around the time of iOS 4.3,
+ // the first call to set the kCFStreamPropertySSLSettings will return true,
+ // but the second will return false.
+ //
+ // Order doesn't seem to matter.
+ // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order.
+ // Either way, the first call will return true, and the second returns false.
+ //
+ // Interestingly, this doesn't seem to affect anything.
+ // Which is not altogether unusual, as the documentation seems to suggest that (for many settings)
+ // setting it on one side of the stream automatically sets it for the other side of the stream.
+ //
+ // Although there isn't anything in the documentation to suggest that the second attempt would fail.
+ //
+ // Furthermore, this only seems to affect streams that are negotiating a security upgrade.
+ // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure
+ // connection, and then a startTLS is issued.
+ // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS).
+
+ if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug.
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]];
+ return;
+ }
+
+ if (![self openStreams])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamOpen"]];
+ return;
+ }
+
+ LogVerbose(@"Waiting for SSL Handshake to complete...");
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
++ (void)ignore:(id)_
+{}
+
++ (void)startCFStreamThreadIfNeeded
+{
+ LogTrace();
+
+ static dispatch_once_t predicate;
+ dispatch_once(&predicate, ^{
+
+ cfstreamThreadRetainCount = 0;
+ cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL);
+ });
+
+ dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+
+ if (++cfstreamThreadRetainCount == 1)
+ {
+ cfstreamThread = [[NSThread alloc] initWithTarget:self
+ selector:@selector(cfstreamThread)
+ object:nil];
+ [cfstreamThread start];
+ }
+ }});
+}
+
++ (void)stopCFStreamThreadIfNeeded
+{
+ LogTrace();
+
+ // The creation of the cfstreamThread is relatively expensive.
+ // So we'd like to keep it available for recycling.
+ // However, there's a tradeoff here, because it shouldn't remain alive forever.
+ // So what we're going to do is use a little delay before taking it down.
+ // This way it can be reused properly in situations where multiple sockets are continually in flux.
+
+ int delayInSeconds = 30;
+ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
+ dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ if (cfstreamThreadRetainCount == 0)
+ {
+ LogWarn(@"Logic error concerning cfstreamThread start / stop");
+ return_from_block;
+ }
+
+ if (--cfstreamThreadRetainCount == 0)
+ {
+ [cfstreamThread cancel]; // set isCancelled flag
+
+ // wake up the thread
+ [[self class] performSelector:@selector(ignore:)
+ onThread:cfstreamThread
+ withObject:[NSNull null]
+ waitUntilDone:NO];
+
+ cfstreamThread = nil;
+ }
+
+ #pragma clang diagnostic pop
+ }});
+}
+
++ (void)cfstreamThread { @autoreleasepool
+{
+ [[NSThread currentThread] setName:GCDAsyncSocketThreadName];
+
+ LogInfo(@"CFStreamThread: Started");
+
+ // We can't run the run loop unless it has an associated input source or a timer.
+ // So we'll just create a timer that will never fire - unless the server runs for decades.
+ [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+ target:self
+ selector:@selector(ignore:)
+ userInfo:nil
+ repeats:YES];
+
+ NSThread *currentThread = [NSThread currentThread];
+ NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
+
+ BOOL isCancelled = [currentThread isCancelled];
+
+ while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
+ {
+ isCancelled = [currentThread isCancelled];
+ }
+
+ LogInfo(@"CFStreamThread: Stopped");
+}}
+
++ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncSocket->readStream)
+ CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncSocket->writeStream)
+ CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncSocket->readStream)
+ CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncSocket->writeStream)
+ CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventHasBytesAvailable:
+ {
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+
+ if (asyncSocket->readStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+ // (A callback related to the tcp stream, but not to the SSL layer).
+
+ if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
+ {
+ asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+ [asyncSocket cf_finishSSLHandshake];
+ }
+ }
+ else
+ {
+ asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+ [asyncSocket doReadData];
+ }
+ }});
+
+ break;
+ }
+ default:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
+
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncSocket connectionClosedError];
+ }
+
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFReadStreamCallback - Other");
+
+ if (asyncSocket->readStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ [asyncSocket cf_abortSSLHandshake:error];
+ }
+ else
+ {
+ [asyncSocket closeWithError:error];
+ }
+ }});
+
+ break;
+ }
+ }
+
+}
+
+static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventCanAcceptBytes:
+ {
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+
+ if (asyncSocket->writeStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+ // (A callback related to the tcp stream, but not to the SSL layer).
+
+ if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
+ {
+ asyncSocket->flags |= kSocketCanAcceptBytes;
+ [asyncSocket cf_finishSSLHandshake];
+ }
+ }
+ else
+ {
+ asyncSocket->flags |= kSocketCanAcceptBytes;
+ [asyncSocket doWriteData];
+ }
+ }});
+
+ break;
+ }
+ default:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncSocket connectionClosedError];
+ }
+
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFWriteStreamCallback - Other");
+
+ if (asyncSocket->writeStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ [asyncSocket cf_abortSSLHandshake:error];
+ }
+ else
+ {
+ [asyncSocket closeWithError:error];
+ }
+ }});
+
+ break;
+ }
+ }
+
+}
+
+- (BOOL)createReadAndWriteStream
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (readStream || writeStream)
+ {
+ // Streams already created
+ return YES;
+ }
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ if (socketFD == SOCKET_NULL)
+ {
+ // Cannot create streams without a file descriptor
+ return NO;
+ }
+
+ if (![self isConnected])
+ {
+ // Cannot create streams until file descriptor is connected
+ return NO;
+ }
+
+ LogVerbose(@"Creating read and write stream...");
+
+ CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
+
+ // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case).
+ // But let's not take any chances.
+
+ if (readStream)
+ CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+ if (writeStream)
+ CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+
+ if ((readStream == NULL) || (writeStream == NULL))
+ {
+ LogWarn(@"Unable to create read and write stream...");
+
+ if (readStream)
+ {
+ CFReadStreamClose(readStream);
+ CFRelease(readStream);
+ readStream = NULL;
+ }
+ if (writeStream)
+ {
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ writeStream = NULL;
+ }
+
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
+{
+ LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ streamContext.version = 0;
+ streamContext.info = (__bridge void *)(self);
+ streamContext.retain = nil;
+ streamContext.release = nil;
+ streamContext.copyDescription = nil;
+
+ CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+ if (includeReadWrite)
+ readStreamEvents |= kCFStreamEventHasBytesAvailable;
+
+ if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
+ {
+ return NO;
+ }
+
+ CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+ if (includeReadWrite)
+ writeStreamEvents |= kCFStreamEventCanAcceptBytes;
+
+ if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)addStreamsToRunLoop
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ if (!(flags & kAddedStreamsToRunLoop))
+ {
+ LogVerbose(@"Adding streams to runloop...");
+
+ [[self class] startCFStreamThreadIfNeeded];
+ [[self class] performSelector:@selector(scheduleCFStreams:)
+ onThread:cfstreamThread
+ withObject:self
+ waitUntilDone:YES];
+
+ flags |= kAddedStreamsToRunLoop;
+ }
+
+ return YES;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ if (flags & kAddedStreamsToRunLoop)
+ {
+ LogVerbose(@"Removing streams from runloop...");
+
+ [[self class] performSelector:@selector(unscheduleCFStreams:)
+ onThread:cfstreamThread
+ withObject:self
+ waitUntilDone:YES];
+ [[self class] stopCFStreamThreadIfNeeded];
+
+ flags &= ~kAddedStreamsToRunLoop;
+ }
+}
+
+- (BOOL)openStreams
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
+ CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream);
+
+ if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
+ {
+ LogVerbose(@"Opening read and write stream...");
+
+ BOOL r1 = CFReadStreamOpen(readStream);
+ BOOL r2 = CFWriteStreamOpen(writeStream);
+
+ if (!r1 || !r2)
+ {
+ LogError(@"Error in CFStreamOpen");
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream
+{
+ // Note: YES means kAllowHalfDuplexConnection is OFF
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kAllowHalfDuplexConnection) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((config & kAllowHalfDuplexConnection) == 0);
+ });
+
+ return result;
+ }
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
+{
+ // Note: YES means kAllowHalfDuplexConnection is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ config &= ~kAllowHalfDuplexConnection;
+ else
+ config |= kAllowHalfDuplexConnection;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
+{
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
+{
+ dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)performBlock:(dispatch_block_t)block
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socketFD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ if (socket4FD != SOCKET_NULL)
+ return socket4FD;
+ else
+ return socket6FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket4FD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket4FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket6FD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFReadStreamRef)readStream
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ if (readStream == NULL)
+ [self createReadAndWriteStream];
+
+ return readStream;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFWriteStreamRef)writeStream
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ if (writeStream == NULL)
+ [self createReadAndWriteStream];
+
+ return writeStream;
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
+{
+ if (![self createReadAndWriteStream])
+ {
+ // Error occurred creating streams (perhaps socket isn't open)
+ return NO;
+ }
+
+ BOOL r1, r2;
+
+ LogVerbose(@"Enabling backgrouding on socket");
+
+ r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+
+ if (!r1 || !r2)
+ {
+ return NO;
+ }
+
+ if (!caveat)
+ {
+ if (![self openStreams])
+ {
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (BOOL)enableBackgroundingOnSocket
+{
+ LogTrace();
+
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NO;
+ }
+
+ return [self enableBackgroundingOnSocketWithCaveat:NO];
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
+{
+ // This method was created as a workaround for a bug in iOS.
+ // Apple has since fixed this bug.
+ // I'm not entirely sure which version of iOS they fixed it in...
+
+ LogTrace();
+
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NO;
+ }
+
+ return [self enableBackgroundingOnSocketWithCaveat:YES];
+}
+
+#endif
+
+- (SSLContextRef)sslContext
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ return sslContext;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSMutableArray *addresses = nil;
+ NSError *error = nil;
+
+ if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+ {
+ // Use LOOPBACK address
+ struct sockaddr_in nativeAddr4;
+ nativeAddr4.sin_len = sizeof(struct sockaddr_in);
+ nativeAddr4.sin_family = AF_INET;
+ nativeAddr4.sin_port = htons(port);
+ nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
+
+ struct sockaddr_in6 nativeAddr6;
+ nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
+ nativeAddr6.sin6_family = AF_INET6;
+ nativeAddr6.sin6_port = htons(port);
+ nativeAddr6.sin6_flowinfo = 0;
+ nativeAddr6.sin6_addr = in6addr_loopback;
+ nativeAddr6.sin6_scope_id = 0;
+
+ // Wrap the native address structures
+
+ NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+
+ addresses = [NSMutableArray arrayWithCapacity:2];
+ [addresses addObject:address4];
+ [addresses addObject:address6];
+ }
+ else
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+
+ if (gai_error)
+ {
+ error = [self gaiError:gai_error];
+ }
+ else
+ {
+ NSUInteger capacity = 0;
+ for (res = res0; res; res = res->ai_next)
+ {
+ if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
+ capacity++;
+ }
+ }
+
+ addresses = [NSMutableArray arrayWithCapacity:capacity];
+
+ for (res = res0; res; res = res->ai_next)
+ {
+ if (res->ai_family == AF_INET)
+ {
+ // Found IPv4 address.
+ // Wrap the native address structure, and add to results.
+
+ NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ [addresses addObject:address4];
+ }
+ else if (res->ai_family == AF_INET6)
+ {
+ // Fixes connection issues with IPv6
+ // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
+
+ // Found IPv6 address.
+ // Wrap the native address structure, and add to results.
+
+ struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr;
+ in_port_t *portPtr = &sockaddr->sin6_port;
+ if ((portPtr != NULL) && (*portPtr == 0)) {
+ *portPtr = htons(port);
+ }
+
+ NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ [addresses addObject:address6];
+ }
+ }
+ freeaddrinfo(res0);
+
+ if ([addresses count] == 0)
+ {
+ error = [self gaiError:EAI_FAIL];
+ }
+ }
+ }
+
+ if (errPtr) *errPtr = error;
+ return addresses;
+}
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ char addrBuf[INET_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ char addrBuf[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
+{
+ NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
+ return [NSURL fileURLWithPath:path];
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+ NSString *host;
+
+ if ([self getHost:&host port:NULL fromAddress:address])
+ return host;
+ else
+ return nil;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+ uint16_t port;
+
+ if ([self getHost:NULL port:&port fromAddress:address])
+ return port;
+ else
+ return 0;
+}
+
++ (BOOL)isIPv4Address:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
++ (BOOL)isIPv6Address:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET6) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+ return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in))
+ {
+ struct sockaddr_in sockaddr4;
+ memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4));
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4];
+ if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4];
+ if (afPtr) *afPtr = AF_INET;
+
+ return YES;
+ }
+ }
+ else if (sockaddrX->sa_family == AF_INET6)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in6))
+ {
+ struct sockaddr_in6 sockaddr6;
+ memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6));
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
+ if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
+ if (afPtr) *afPtr = AF_INET6;
+
+ return YES;
+ }
+ }
+ }
+
+ return NO;
+}
+
++ (NSData *)CRLFData
+{
+ return [NSData dataWithBytes:"\x0D\x0A" length:2];
+}
+
++ (NSData *)CRData
+{
+ return [NSData dataWithBytes:"\x0D" length:1];
+}
+
++ (NSData *)LFData
+{
+ return [NSData dataWithBytes:"\x0A" length:1];
+}
+
++ (NSData *)ZeroData
+{
+ return [NSData dataWithBytes:"" length:1];
+}
+
+@end
diff --git a/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
new file mode 100755
index 0000000..7dc1615
--- /dev/null
+++ b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
@@ -0,0 +1,1009 @@
+//
+// 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
+#import
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+extern NSString *const GCDAsyncUdpSocketException;
+extern NSString *const GCDAsyncUdpSocketErrorDomain;
+
+extern NSString *const GCDAsyncUdpSocketQueueName;
+extern NSString *const GCDAsyncUdpSocketThreadName;
+
+typedef NS_ENUM(NSInteger, GCDAsyncUdpSocketError) {
+ GCDAsyncUdpSocketNoError = 0, // Never used
+ GCDAsyncUdpSocketBadConfigError, // Invalid configuration
+ GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed
+ GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out
+ GCDAsyncUdpSocketClosedError, // The socket was closed
+ GCDAsyncUdpSocketOtherError, // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@class GCDAsyncUdpSocket;
+
+@protocol GCDAsyncUdpSocketDelegate
+@optional
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ *
+ * This method is called if one of the connect methods are invoked, and the connection is successful.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address;
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ *
+ * This method is called if one of the connect methods are invoked, and the connection fails.
+ * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error;
+
+/**
+ * Called when the datagram with the given tag has been sent.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
+
+/**
+ * Called if an error occurs while trying to send a datagram.
+ * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error;
+
+/**
+ * Called when the socket has received the requested datagram.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
+ fromAddress:(NSData *)address
+ withFilterContext:(nullable id)filterContext;
+
+/**
+ * Called when the socket is closed.
+**/
+- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error;
+
+@end
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * A filter can provide several useful features:
+ *
+ * 1. Many times udp packets need to be parsed.
+ * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ * The end result is a parallel socket io, datagram parsing, and packet processing.
+ *
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ * The filter can prevent such packets from arriving at the delegate.
+ * And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ *
+ * - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ * Many protocols built atop udp thus provide various resend/re-request algorithms.
+ * This sometimes results in duplicate packets arriving.
+ * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *
+ * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ * Such packets need to be ignored.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * @param data - The packet that was received.
+ * @param address - The address the data was received from.
+ * See utilities section for methods to extract info from address.
+ * @param context - Out parameter you may optionally set, which will then be passed to the delegate method.
+ * For example, filter block can parse the data and then,
+ * pass the parsed data to the delegate.
+ *
+ * @returns - YES if the received packet should be passed onto the delegate.
+ * NO if the received packet should be discarded, and not reported to the delegete.
+ *
+ * Example:
+ *
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ *
+ * MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *
+ * *context = response;
+ * return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ *
+**/
+typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context);
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ *
+ * 1. Optional caching of resolved addresses for domain names.
+ * The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ *
+ * 2. Reusable modules of code for bandwidth monitoring.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * @param data - The packet that was received.
+ * @param address - The address the data was received from.
+ * See utilities section for methods to extract info from address.
+ * @param tag - The tag that was passed in the send method.
+ *
+ * @returns - YES if the packet should actually be sent over the socket.
+ * NO if the packet should be silently dropped (not sent over the socket).
+ *
+ * Regardless of the return value, the delegate will be informed that the packet was successfully sent.
+ *
+**/
+typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag);
+
+
+@interface GCDAsyncUdpSocket : NSObject
+
+/**
+ * GCDAsyncUdpSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ *
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ *
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ *
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq;
+
+#pragma mark Configuration
+
+- (nullable id )delegate;
+- (void)setDelegate:(nullable id )delegate;
+- (void)synchronouslySetDelegate:(nullable id )delegate;
+
+- (nullable dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ *
+ * This means GCDAsyncUdpSocket automatically supports both protocols,
+ * and can send to IPv4 or IPv6 addresses,
+ * as well as receive over IPv4 and IPv6.
+ *
+ * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference.
+ * If IPv4 is preferred, then IPv4 is used.
+ * If IPv6 is preferred, then IPv6 is used.
+ * If neutral, then the first IP version in the resolved array will be used.
+ *
+ * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral.
+ * On prior systems the default IP preference is IPv4.
+ **/
+- (BOOL)isIPv4Enabled;
+- (void)setIPv4Enabled:(BOOL)flag;
+
+- (BOOL)isIPv6Enabled;
+- (void)setIPv6Enabled:(BOOL)flag;
+
+- (BOOL)isIPv4Preferred;
+- (BOOL)isIPv6Preferred;
+- (BOOL)isIPVersionNeutral;
+
+- (void)setPreferIPv4;
+- (void)setPreferIPv6;
+- (void)setIPVersionNeutral;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
+ * The default maximum size is 9216 bytes.
+ *
+ * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+ * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+ *
+ * Since the OS/GCD notifies us of the size of each received UDP packet,
+ * the actual allocated buffer size for each packet is exact.
+ * And in practice the size of UDP packets is generally much smaller than the max.
+ * Indeed most protocols will send and receive packets of only a few bytes,
+ * or will set a limit on the size of packets to prevent fragmentation in the IP layer.
+ *
+ * If you set the buffer size too small, the sockets API in the OS will silently discard
+ * any extra data, and you will not be notified of the error.
+**/
+- (uint16_t)maxReceiveIPv4BufferSize;
+- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max;
+
+- (uint32_t)maxReceiveIPv6BufferSize;
+- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally in any way.
+**/
+- (nullable id)userData;
+- (void)setUserData:(nullable id)arbitraryUserData;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns the local address info for the socket.
+ *
+ * The localAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The localHost method returns the human readable IP address as a string.
+ *
+ * Note: Address info may not be available until after the socket has been binded, connected
+ * or until after data has been sent.
+**/
+- (nullable NSData *)localAddress;
+- (nullable NSString *)localHost;
+- (uint16_t)localPort;
+
+- (nullable NSData *)localAddress_IPv4;
+- (nullable NSString *)localHost_IPv4;
+- (uint16_t)localPort_IPv4;
+
+- (nullable NSData *)localAddress_IPv6;
+- (nullable NSString *)localHost_IPv6;
+- (uint16_t)localPort_IPv6;
+
+/**
+ * Returns the remote address info for the socket.
+ *
+ * The connectedAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The connectedHost method returns the human readable IP address as a string.
+ *
+ * Note: Since UDP is connectionless by design, connected address info
+ * will not be available unless the socket is explicitly connected to a remote host/port.
+ * If the socket is not connected, these methods will return nil / 0.
+**/
+- (nullable NSData *)connectedAddress;
+- (nullable NSString *)connectedHost;
+- (uint16_t)connectedPort;
+
+/**
+ * Returns whether or not this socket has been connected to a single host.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * If connected, the socket will only be able to send/receive data to/from the connected host.
+**/
+- (BOOL)isConnected;
+
+/**
+ * Returns whether or not this socket has been closed.
+ * The only way a socket can be closed is if you explicitly call one of the close methods.
+**/
+- (BOOL)isClosed;
+
+/**
+ * Returns whether or not this socket is IPv4.
+ *
+ * By default this will be true, unless:
+ * - IPv4 is disabled (via setIPv4Enabled:)
+ * - The socket is explicitly bound to an IPv6 address
+ * - The socket is connected to an IPv6 address
+**/
+- (BOOL)isIPv4;
+
+/**
+ * Returns whether or not this socket is IPv6.
+ *
+ * By default this will be true, unless:
+ * - IPv6 is disabled (via setIPv6Enabled:)
+ * - The socket is explicitly bound to an IPv4 address
+ * _ The socket is connected to an IPv4 address
+ *
+ * This method will also return false on platforms that do not support IPv6.
+ * Note: The iPhone does not currently support IPv6.
+**/
+- (BOOL)isIPv6;
+
+#pragma mark Binding
+
+/**
+ * Binds the UDP socket to the given port.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given port and optional interface.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept packets from the local machine.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects the UDP socket to the given host and port.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ *
+ * Choosing to connect to a specific host/port has the following effect:
+ * - You will only be able to send data to the connected host/port.
+ * - You will only be able to receive data from the connected host/port.
+ * - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
+ *
+ * The actual process of connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ *
+ * You cannot bind a socket after it has been connected.
+ * You can only connect a socket once.
+ *
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ *
+ * This method is asynchronous as it requires a DNS lookup to resolve the given host name.
+ * If an obvious error is detected, this method immediately returns NO and sets errPtr.
+ * If you don't care about the error, you can pass nil for errPtr.
+ * Otherwise, this method returns YES and begins the asynchronous connection process.
+ * The result of the asynchronous connection process will be reported via the delegate methods.
+ **/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ *
+ * Choosing to connect to a specific address has the following effect:
+ * - You will only be able to send data to the connected address.
+ * - You will only be able to receive data from the connected address.
+ * - You will receive ICMP messages that come from the connected address, such as "connection refused".
+ *
+ * Connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only connect a socket once.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+ *
+ * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup.
+ * Thus when this method returns, the connection has either failed or fully completed.
+ * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method.
+ * However, for compatibility and simplification of delegate code, if this method returns YES
+ * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+#pragma mark Multicast
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+#pragma mark Reuse Port
+
+/**
+ * By default, only one socket can be bound to a given IP address + port at a time.
+ * To enable multiple processes to simultaneously bind to the same address+port,
+ * you need to enable this functionality in the socket. All processes that wish to
+ * use the address+port simultaneously must all enable reuse port on the socket
+ * bound to that port.
+ **/
+- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Broadcast
+
+/**
+ * By default, the underlying socket in the OS will not allow you to send broadcast messages.
+ * In order to send broadcast messages, you need to enable this functionality in the socket.
+ *
+ * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
+ * delivered to every host on the network.
+ * The reason this is generally disabled by default (by the OS) is to prevent
+ * accidental broadcast messages from flooding the network.
+**/
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Sending
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag.
+ *
+ * This method may only be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
+ *
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param host
+ * The destination to send the udp packet to.
+ * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * You may also use the convenience strings of "loopback" or "localhost".
+ *
+ * @param port
+ * The port of the host to send to.
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data
+ toHost:(NSString *)host
+ port:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given address.
+ *
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param remoteAddr
+ * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object).
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ *
+ * 1. Optional caching of resolved addresses for domain names.
+ * The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ *
+ * 2. Reusable modules of code for bandwidth monitoring.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ *
+ * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below),
+ * passing YES for the isAsynchronous parameter.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ *
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ *
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock
+ withQueue:(nullable dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Receiving
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ *
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ *
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ *
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode.
+ *
+ * When a packet is received (and not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ *
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ *
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)receiveOnce:(NSError **)errPtr;
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ *
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ *
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ *
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode.
+ *
+ * For every received packet (not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ *
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ *
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)beginReceiving:(NSError **)errPtr;
+
+/**
+ * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving.
+ * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again.
+ *
+ * Important Note:
+ * GCDAsyncUdpSocket may be running in parallel with your code.
+ * That is, your delegate is likely running on a separate thread/dispatch_queue.
+ * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked.
+ * Thus, if those delegate methods have already been dispatch_async'd,
+ * your didReceive delegate method may still be invoked after this method has been called.
+ * You should be aware of this, and program defensively.
+**/
+- (void)pauseReceiving;
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * This receive filter may be set to run in its own queue (independent of delegate queue).
+ *
+ * A filter can provide several useful features.
+ *
+ * 1. Many times udp packets need to be parsed.
+ * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ * The end result is a parallel socket io, datagram parsing, and packet processing.
+ *
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ * The filter can prevent such packets from arriving at the delegate.
+ * And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ *
+ * - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ * Many protocols built atop udp thus provide various resend/re-request algorithms.
+ * This sometimes results in duplicate packets arriving.
+ * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *
+ * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ * Such packets need to be ignored.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * Example:
+ *
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ *
+ * MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *
+ * *context = response;
+ * return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ *
+ * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ *
+ * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below),
+ * passing YES for the isAsynchronous parameter.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ *
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ *
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+ withQueue:(nullable dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Closing
+
+/**
+ * Immediately closes the underlying socket.
+ * Any pending send operations are discarded.
+ *
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ * (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)close;
+
+/**
+ * Closes the underlying socket after all pending send operations have been sent.
+ *
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ * (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)closeAfterSending;
+
+#pragma mark Advanced
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ * __block BOOL result = NO;
+ * dispatch_block_t block = ^{
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ * }
+ * if (is_executing_on_queue(socketQueue))
+ * block();
+ * else
+ * dispatch_sync(socketQueue, block);
+ *
+ * return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ * dispatch_set_target_queue(socketQueue, ipQueue);
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ * return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ * [clientConnections addObject:newSocket];
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+ **/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ *
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ *
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ *
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ *
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ *
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's file descriptor(s).
+ * If the socket isn't connected, or explicity bound to a particular interface,
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket.
+ *
+ * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.)
+ * However, if you need one for any reason,
+ * these methods are a convenient way to get access to a safe instance of one.
+**/
+- (CFReadStreamRef)readStream;
+- (CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ *
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ *
+ * Returns YES if successful, NO otherwise.
+ *
+ * Example usage:
+ *
+ * [asyncUdpSocket performBlock:^{
+ * [asyncUdpSocket enableBackgroundingOnSocket];
+ * }];
+ *
+ *
+ * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now).
+**/
+//- (BOOL)enableBackgroundingOnSockets;
+
+#endif
+
+#pragma mark Utilities
+
+/**
+ * Extracting host/port/family information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (int)familyFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
new file mode 100755
index 0000000..27c739d
--- /dev/null
+++ b/needle-agent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
@@ -0,0 +1,5430 @@
+//
+// 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
+ #import
+#endif
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+
+#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();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
+ {
+ NSData *address = [anAddress copy]; // In case param is NSMutableData
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didConnectToAddress:address];
+ }});
+ }
+}
+
+- (void)notifyDidNotConnect:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didNotConnect:error];
+ }});
+ }
+}
+
+- (void)notifyDidSendDataWithTag:(long)tag
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didSendDataWithTag:tag];
+ }});
+ }
+}
+
+- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
+ {
+ 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:);
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:selector])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
+ }});
+ }
+}
+
+- (void)notifyDidCloseWithError:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
+ {
+ 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 Reuse port
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)enableReusePort:(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;
+ }
+ }
+
+ int value = flag ? 1 : 0;
+ if (socket4FD != SOCKET_NULL)
+ {
+ int error = setsockopt(socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+
+ if (error)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+ result = YES;
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ int error = setsockopt(socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+
+ if (error)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+ result = YES;
+ }
+
+ }};
+
+ 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);
+
+ // #222: GCD does not necessarily return the size of an entire UDP packet
+ // from dispatch_source_get_data(), so we must use the maximum packet size.
+ size_t bufSize = max4ReceiveSize;
+ void *buf = malloc(bufSize);
+
+ result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
+ LogVerbose(@"recvfrom(socket4FD) = %i", (int)result);
+
+ if (result > 0)
+ {
+ if ((size_t)result >= socket4FDBytesAvailable)
+ socket4FDBytesAvailable = 0;
+ else
+ socket4FDBytesAvailable -= result;
+
+ if ((size_t)result != bufSize) {
+ buf = realloc(buf, result);
+ }
+
+ data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+ addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ else
+ {
+ LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]);
+ socket4FDBytesAvailable = 0;
+ free(buf);
+ }
+ }
+ else
+ {
+ NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic");
+ LogVerbose(@"Receiving on IPv6");
+
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ // #222: GCD does not necessarily return the size of an entire UDP packet
+ // from dispatch_source_get_data(), so we must use the maximum packet size.
+ size_t bufSize = max6ReceiveSize;
+ void *buf = malloc(bufSize);
+
+ result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
+ LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result);
+
+ if (result > 0)
+ {
+ if ((size_t)result >= socket6FDBytesAvailable)
+ socket6FDBytesAvailable = 0;
+ else
+ socket6FDBytesAvailable -= result;
+
+ if ((size_t)result != bufSize) {
+ buf = realloc(buf, result);
+ }
+
+ data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+ addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ else
+ {
+ LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]);
+ socket6FDBytesAvailable = 0;
+ free(buf);
+ }
+ }
+
+
+ BOOL waitingForSocket = NO;
+ BOOL notifiedDelegate = NO;
+ BOOL ignored = NO;
+
+ NSError *socketError = nil;
+
+ if (result == 0)
+ {
+ waitingForSocket = YES;
+ }
+ else if (result < 0)
+ {
+ if (errno == EAGAIN)
+ waitingForSocket = YES;
+ else
+ socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"];
+ }
+ else
+ {
+ if (flags & kDidConnect)
+ {
+ if (addr4 && ![self isConnectedToAddress4:addr4])
+ ignored = YES;
+ if (addr6 && ![self isConnectedToAddress6:addr6])
+ ignored = YES;
+ }
+
+ NSData *addr = (addr4 != nil) ? addr4 : addr6;
+
+ if (!ignored)
+ {
+ if (receiveFilterBlock && receiveFilterQueue)
+ {
+ // Run data through filter, and if approved, notify delegate
+
+ __block id filterContext = nil;
+ __block BOOL allowed = NO;
+
+ if (receiveFilterAsync)
+ {
+ pendingFilterOperations++;
+ dispatch_async(receiveFilterQueue, ^{ @autoreleasepool {
+
+ allowed = 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
diff --git a/needle-agent/Pods/Manifest.lock b/needle-agent/Pods/Manifest.lock
new file mode 100755
index 0000000..b5cfcfd
--- /dev/null
+++ b/needle-agent/Pods/Manifest.lock
@@ -0,0 +1,14 @@
+PODS:
+ - CocoaAsyncSocket (7.5.0):
+ - CocoaAsyncSocket/GCD (= 7.5.0)
+ - CocoaAsyncSocket/GCD (7.5.0)
+
+DEPENDENCIES:
+ - CocoaAsyncSocket
+
+SPEC CHECKSUMS:
+ CocoaAsyncSocket: 3baeb1ddd969f81cf9fca81053ae49ef2d1cbbfa
+
+PODFILE CHECKSUM: 7c5957ad98cb9e0ab53e09b0d15e8d0fa3d69ccb
+
+COCOAPODS: 1.0.1
diff --git a/needle-agent/Pods/Pods.xcodeproj/project.pbxproj b/needle-agent/Pods/Pods.xcodeproj/project.pbxproj
new file mode 100755
index 0000000..3720d96
--- /dev/null
+++ b/needle-agent/Pods/Pods.xcodeproj/project.pbxproj
@@ -0,0 +1,847 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 0AF7C5A1FC7A4EFDE8208E74E92E7AD4 /* Pods-needleAgent-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C88CD19AF854207F670C9DC9DA0412AC /* Pods-needleAgent-dummy.m */; };
+ 2091BCB6537476FE68D25B52920EB6A8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */; };
+ 43510B30BCD8F9AAF3E23E2DED42D150 /* Pods-needleAgent-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F2636C475D7295189F30F3A651AA74CD /* Pods-needleAgent-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 54314391F09C12F753B7DD806DF33521 /* Pods-needleAgentTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C0E4BE251D5506451805FD51CF0B132 /* Pods-needleAgentTests-dummy.m */; };
+ 6A896539750DB2BAADE03CC2B70AC113 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */; };
+ 6AAFA2F344A6B58F728AF958F3FE6D89 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CA8FFB2F306345702A20E3FE7CE6EF7 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 74303F614D7B89EF02CD0A94B7AE653A /* Pods-needleAgentUITests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E438F399C2ECFA6A87BB58D69FA66D8F /* Pods-needleAgentUITests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 924AA559AC002D2281573E6CB1DADB07 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */; };
+ 92AED971B91C640191A5ABA1567FE358 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A110336E93C3E786F9B32956E29A3E /* GCDAsyncUdpSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 991BFD139F6A45C9B654960C2F6A7F09 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D696A8CDEBE87CDE80E57AE0E305C5F /* GCDAsyncSocket.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
+ A78B1D34489977909D2AB901CD69FB56 /* Pods-needleAgentTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F97791D397162DB94B6D69B86FFBDB /* Pods-needleAgentTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ ACFE60AB34A7FEF697E6EEFB62436A8D /* CocoaAsyncSocket-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = D6D9F3F23E9E53DAFA379995A9E13E58 /* CocoaAsyncSocket-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ B1C36F8E1B1D69D37812562EF03FA7A4 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5135EC14FA83C331E94D40C19DAB0764 /* CFNetwork.framework */; };
+ C920884F6B4223EE534F25AD1F5781C8 /* Pods-needleAgentUITests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3BF4AC405BFF583EB9D81770645D2 /* Pods-needleAgentUITests-dummy.m */; };
+ CEFE28420773FC61D2DB4778ABDC12A1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */; };
+ D9A44696356E299F3C9A4BFF9E57343F /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 098765466D421180F3953EF8EEDDCEF1 /* GCDAsyncUdpSocket.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
+ F45B40DF0BF596BDCD96B2D85EF848FA /* CocoaAsyncSocket-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E98378F2CF04086E8088C6A14347C33 /* CocoaAsyncSocket-dummy.m */; };
+ F87F607CCD51536EC9CAD7F1488845C6 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CAF4AB1ECD042E4FED766B8370F6530 /* Security.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 03F6E93C16B3215F216609B09915880D /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = B37E19CA872A7535725D1E1946E5C271;
+ remoteInfo = CocoaAsyncSocket;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 098765466D421180F3953EF8EEDDCEF1 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncUdpSocket.m; path = Source/GCD/GCDAsyncUdpSocket.m; sourceTree = ""; };
+ 13DFF73D1E951D4A997B3AD2A0FBD7E8 /* Pods-needleAgent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgent.debug.xcconfig"; sourceTree = ""; };
+ 169817E03CB1BCCBD39DEB030A241314 /* Pods-needleAgentTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgentTests.debug.xcconfig"; sourceTree = ""; };
+ 1D696A8CDEBE87CDE80E57AE0E305C5F /* GCDAsyncSocket.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = Source/GCD/GCDAsyncSocket.m; sourceTree = ""; };
+ 293EA17C485A81E233632E9D73E42EDF /* Pods-needleAgentUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgentUITests.release.xcconfig"; sourceTree = ""; };
+ 2EF436B09B560AF848B4D98C55F61FE0 /* CocoaAsyncSocket.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = CocoaAsyncSocket.xcconfig; sourceTree = ""; };
+ 34E3BF4AC405BFF583EB9D81770645D2 /* Pods-needleAgentUITests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-needleAgentUITests-dummy.m"; sourceTree = ""; };
+ 394ED3AB63206DBD2D2446E72238A221 /* Pods-needleAgentTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgentTests.release.xcconfig"; sourceTree = ""; };
+ 3AA57787FADEC4C4C8F431B40874E9B9 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 4612E4564F903090BE9E017538D77538 /* Pods-needleAgentUITests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-needleAgentUITests-acknowledgements.plist"; sourceTree = ""; };
+ 4A367721E7EE4F97DF1A571E230690EE /* Pods-needleAgent-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-needleAgent-acknowledgements.plist"; sourceTree = ""; };
+ 4CA8FFB2F306345702A20E3FE7CE6EF7 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = Source/GCD/GCDAsyncSocket.h; sourceTree = ""; };
+ 5135EC14FA83C331E94D40C19DAB0764 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
+ 6B19FF960410818D4B37CF046246B95B /* Pods_needleAgentUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_needleAgentUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6DC5C4CDE994DFB5A1BFDE65CAD23B01 /* Pods-needleAgent-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-needleAgent-acknowledgements.markdown"; sourceTree = ""; };
+ 75F9206B99E5B5423D6B61D755C011CC /* CocoaAsyncSocket-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "CocoaAsyncSocket-prefix.pch"; sourceTree = ""; };
+ 7AED7BC1C0F626BAB910E8885FE7FD98 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7BF532EFA9FDE84C0364AE2C965DF227 /* Pods-needleAgentTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgentTests-frameworks.sh"; sourceTree = ""; };
+ 7C542BFA3ABF6AFB21CD54A6FA886939 /* Pods-needleAgentUITests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-needleAgentUITests.modulemap"; sourceTree = ""; };
+ 7CAF4AB1ECD042E4FED766B8370F6530 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
+ 7ED9E4FF9EA5D1DF7FE3C8807B399B37 /* Pods-needleAgentTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgentTests-resources.sh"; sourceTree = ""; };
+ 84A110336E93C3E786F9B32956E29A3E /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = Source/GCD/GCDAsyncUdpSocket.h; sourceTree = ""; };
+ 86340A99F20BEC0D9E6F18F9080B8720 /* CocoaAsyncSocket.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = CocoaAsyncSocket.modulemap; sourceTree = ""; };
+ 877C964C6B106941A9A62565894EC8F3 /* Pods-needleAgentUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgentUITests.debug.xcconfig"; sourceTree = ""; };
+ 8897EF90B2D55946A61AD5A10C38B75C /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 8E98378F2CF04086E8088C6A14347C33 /* CocoaAsyncSocket-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "CocoaAsyncSocket-dummy.m"; sourceTree = ""; };
+ 8EBCECBD0227A101C058138D02C00A3E /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 923B6A13D6CC8E6B9FEA4CD98C79BC71 /* Pods-needleAgent-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgent-frameworks.sh"; sourceTree = ""; };
+ 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
+ 9C0E4BE251D5506451805FD51CF0B132 /* Pods-needleAgentTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-needleAgentTests-dummy.m"; sourceTree = ""; };
+ 9D1BF32CD95083170C6029D91AD74703 /* Pods-needleAgentTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-needleAgentTests.modulemap"; sourceTree = ""; };
+ A5111B7A008A1807B8D10F452E3DC129 /* Pods-needleAgent-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgent-resources.sh"; sourceTree = ""; };
+ AE36BC6F97B257A3A33F7235C36E831F /* Pods-needleAgentTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-needleAgentTests-acknowledgements.plist"; sourceTree = ""; };
+ AF4F835D928A91621BA7C3E86FE71F19 /* Pods-needleAgent.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-needleAgent.modulemap"; sourceTree = ""; };
+ B4833DA00A84017215D0266C73C46C03 /* Pods_needleAgent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_needleAgent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ C3F97791D397162DB94B6D69B86FFBDB /* Pods-needleAgentTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-needleAgentTests-umbrella.h"; sourceTree = ""; };
+ C83024FC9BEB9C1282FCB4A5FA063410 /* Pods-needleAgentUITests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-needleAgentUITests-acknowledgements.markdown"; sourceTree = ""; };
+ C88CD19AF854207F670C9DC9DA0412AC /* Pods-needleAgent-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-needleAgent-dummy.m"; sourceTree = ""; };
+ D6D9F3F23E9E53DAFA379995A9E13E58 /* CocoaAsyncSocket-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "CocoaAsyncSocket-umbrella.h"; sourceTree = ""; };
+ D7EE570C4D8E5178092DFC661D18A584 /* Pods-needleAgentUITests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgentUITests-resources.sh"; sourceTree = ""; };
+ DB8F994EDE49809A0295B78CCB5E797E /* Pods-needleAgent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-needleAgent.release.xcconfig"; sourceTree = ""; };
+ DF88BA07A226CCA0C64B6EE224B65DBE /* Pods-needleAgentUITests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-needleAgentUITests-frameworks.sh"; sourceTree = ""; };
+ E438F399C2ECFA6A87BB58D69FA66D8F /* Pods-needleAgentUITests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-needleAgentUITests-umbrella.h"; sourceTree = ""; };
+ EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
+ EB95446D0FA93F06D1FC72BE5DE3D536 /* Pods-needleAgentTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-needleAgentTests-acknowledgements.markdown"; sourceTree = ""; };
+ EC1116D39076A6F6F02A66758AC9A1A3 /* Pods_needleAgentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_needleAgentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F2636C475D7295189F30F3A651AA74CD /* Pods-needleAgent-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-needleAgent-umbrella.h"; sourceTree = ""; };
+ F9042062DDF6ABF8D82418E3CE687641 /* CocoaAsyncSocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CocoaAsyncSocket.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 1977D0BD1CC34A54660D4C46B1E983BA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2091BCB6537476FE68D25B52920EB6A8 /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ B9ED45E2D8B8BCFB22AEA1A0810ABD96 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ B1C36F8E1B1D69D37812562EF03FA7A4 /* CFNetwork.framework in Frameworks */,
+ CEFE28420773FC61D2DB4778ABDC12A1 /* Foundation.framework in Frameworks */,
+ F87F607CCD51536EC9CAD7F1488845C6 /* Security.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ C604EE229AAC6913F3AFD92137C29616 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6A896539750DB2BAADE03CC2B70AC113 /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ F060F2D84868DD5152A5171CA7776D93 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 924AA559AC002D2281573E6CB1DADB07 /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 0BBD3C5634B240BE43D40B45EBDFBD9B /* Targets Support Files */ = {
+ isa = PBXGroup;
+ children = (
+ 2A9398571E4D9E89B16661CF5D0DF94C /* Pods-needleAgent */,
+ FB50B76414550AB7FDB28A5D595CBBD1 /* Pods-needleAgentTests */,
+ BACD53C1F92EA6935AF2A4F3EFE177F3 /* Pods-needleAgentUITests */,
+ );
+ name = "Targets Support Files";
+ sourceTree = "";
+ };
+ 1178F420F04662BB9B205EF659BBA2AB /* iOS */ = {
+ isa = PBXGroup;
+ children = (
+ 5135EC14FA83C331E94D40C19DAB0764 /* CFNetwork.framework */,
+ EABC45C2732A4ED943A7E8ECF24F14F7 /* Foundation.framework */,
+ 7CAF4AB1ECD042E4FED766B8370F6530 /* Security.framework */,
+ );
+ name = iOS;
+ sourceTree = "";
+ };
+ 122DA2E5084A4393C29BE363C764795C /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 1178F420F04662BB9B205EF659BBA2AB /* iOS */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 2A9398571E4D9E89B16661CF5D0DF94C /* Pods-needleAgent */ = {
+ isa = PBXGroup;
+ children = (
+ 8EBCECBD0227A101C058138D02C00A3E /* Info.plist */,
+ AF4F835D928A91621BA7C3E86FE71F19 /* Pods-needleAgent.modulemap */,
+ 6DC5C4CDE994DFB5A1BFDE65CAD23B01 /* Pods-needleAgent-acknowledgements.markdown */,
+ 4A367721E7EE4F97DF1A571E230690EE /* Pods-needleAgent-acknowledgements.plist */,
+ C88CD19AF854207F670C9DC9DA0412AC /* Pods-needleAgent-dummy.m */,
+ 923B6A13D6CC8E6B9FEA4CD98C79BC71 /* Pods-needleAgent-frameworks.sh */,
+ A5111B7A008A1807B8D10F452E3DC129 /* Pods-needleAgent-resources.sh */,
+ F2636C475D7295189F30F3A651AA74CD /* Pods-needleAgent-umbrella.h */,
+ 13DFF73D1E951D4A997B3AD2A0FBD7E8 /* Pods-needleAgent.debug.xcconfig */,
+ DB8F994EDE49809A0295B78CCB5E797E /* Pods-needleAgent.release.xcconfig */,
+ );
+ name = "Pods-needleAgent";
+ path = "Target Support Files/Pods-needleAgent";
+ sourceTree = "";
+ };
+ 3750E20CAB4987ACB8AB27B6C25C1D60 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ B16CCCC916CD9D13BBF53EA0C69C4615 /* CocoaAsyncSocket */,
+ );
+ name = Pods;
+ sourceTree = "";
+ };
+ 557C7CF8D4466D636F16673BAB31EEF1 /* Support Files */ = {
+ isa = PBXGroup;
+ children = (
+ 86340A99F20BEC0D9E6F18F9080B8720 /* CocoaAsyncSocket.modulemap */,
+ 2EF436B09B560AF848B4D98C55F61FE0 /* CocoaAsyncSocket.xcconfig */,
+ 8E98378F2CF04086E8088C6A14347C33 /* CocoaAsyncSocket-dummy.m */,
+ 75F9206B99E5B5423D6B61D755C011CC /* CocoaAsyncSocket-prefix.pch */,
+ D6D9F3F23E9E53DAFA379995A9E13E58 /* CocoaAsyncSocket-umbrella.h */,
+ 8897EF90B2D55946A61AD5A10C38B75C /* Info.plist */,
+ );
+ name = "Support Files";
+ path = "../Target Support Files/CocoaAsyncSocket";
+ sourceTree = "";
+ };
+ 7DB346D0F39D3F0E887471402A8071AB = {
+ isa = PBXGroup;
+ children = (
+ 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */,
+ 122DA2E5084A4393C29BE363C764795C /* Frameworks */,
+ 3750E20CAB4987ACB8AB27B6C25C1D60 /* Pods */,
+ F7AD0F2AB5904C988FD559BCDEFC9198 /* Products */,
+ 0BBD3C5634B240BE43D40B45EBDFBD9B /* Targets Support Files */,
+ );
+ sourceTree = "";
+ };
+ 982AE8715AD34D5D99A50B615809B1F1 /* GCD */ = {
+ isa = PBXGroup;
+ children = (
+ 4CA8FFB2F306345702A20E3FE7CE6EF7 /* GCDAsyncSocket.h */,
+ 1D696A8CDEBE87CDE80E57AE0E305C5F /* GCDAsyncSocket.m */,
+ 84A110336E93C3E786F9B32956E29A3E /* GCDAsyncUdpSocket.h */,
+ 098765466D421180F3953EF8EEDDCEF1 /* GCDAsyncUdpSocket.m */,
+ );
+ name = GCD;
+ sourceTree = "";
+ };
+ B16CCCC916CD9D13BBF53EA0C69C4615 /* CocoaAsyncSocket */ = {
+ isa = PBXGroup;
+ children = (
+ 982AE8715AD34D5D99A50B615809B1F1 /* GCD */,
+ 557C7CF8D4466D636F16673BAB31EEF1 /* Support Files */,
+ );
+ path = CocoaAsyncSocket;
+ sourceTree = "";
+ };
+ BACD53C1F92EA6935AF2A4F3EFE177F3 /* Pods-needleAgentUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 7AED7BC1C0F626BAB910E8885FE7FD98 /* Info.plist */,
+ 7C542BFA3ABF6AFB21CD54A6FA886939 /* Pods-needleAgentUITests.modulemap */,
+ C83024FC9BEB9C1282FCB4A5FA063410 /* Pods-needleAgentUITests-acknowledgements.markdown */,
+ 4612E4564F903090BE9E017538D77538 /* Pods-needleAgentUITests-acknowledgements.plist */,
+ 34E3BF4AC405BFF583EB9D81770645D2 /* Pods-needleAgentUITests-dummy.m */,
+ DF88BA07A226CCA0C64B6EE224B65DBE /* Pods-needleAgentUITests-frameworks.sh */,
+ D7EE570C4D8E5178092DFC661D18A584 /* Pods-needleAgentUITests-resources.sh */,
+ E438F399C2ECFA6A87BB58D69FA66D8F /* Pods-needleAgentUITests-umbrella.h */,
+ 877C964C6B106941A9A62565894EC8F3 /* Pods-needleAgentUITests.debug.xcconfig */,
+ 293EA17C485A81E233632E9D73E42EDF /* Pods-needleAgentUITests.release.xcconfig */,
+ );
+ name = "Pods-needleAgentUITests";
+ path = "Target Support Files/Pods-needleAgentUITests";
+ sourceTree = "";
+ };
+ F7AD0F2AB5904C988FD559BCDEFC9198 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ F9042062DDF6ABF8D82418E3CE687641 /* CocoaAsyncSocket.framework */,
+ B4833DA00A84017215D0266C73C46C03 /* Pods_needleAgent.framework */,
+ EC1116D39076A6F6F02A66758AC9A1A3 /* Pods_needleAgentTests.framework */,
+ 6B19FF960410818D4B37CF046246B95B /* Pods_needleAgentUITests.framework */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ FB50B76414550AB7FDB28A5D595CBBD1 /* Pods-needleAgentTests */ = {
+ isa = PBXGroup;
+ children = (
+ 3AA57787FADEC4C4C8F431B40874E9B9 /* Info.plist */,
+ 9D1BF32CD95083170C6029D91AD74703 /* Pods-needleAgentTests.modulemap */,
+ EB95446D0FA93F06D1FC72BE5DE3D536 /* Pods-needleAgentTests-acknowledgements.markdown */,
+ AE36BC6F97B257A3A33F7235C36E831F /* Pods-needleAgentTests-acknowledgements.plist */,
+ 9C0E4BE251D5506451805FD51CF0B132 /* Pods-needleAgentTests-dummy.m */,
+ 7BF532EFA9FDE84C0364AE2C965DF227 /* Pods-needleAgentTests-frameworks.sh */,
+ 7ED9E4FF9EA5D1DF7FE3C8807B399B37 /* Pods-needleAgentTests-resources.sh */,
+ C3F97791D397162DB94B6D69B86FFBDB /* Pods-needleAgentTests-umbrella.h */,
+ 169817E03CB1BCCBD39DEB030A241314 /* Pods-needleAgentTests.debug.xcconfig */,
+ 394ED3AB63206DBD2D2446E72238A221 /* Pods-needleAgentTests.release.xcconfig */,
+ );
+ name = "Pods-needleAgentTests";
+ path = "Target Support Files/Pods-needleAgentTests";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 10BCE19BF62229B9FEE320CE9994119A /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 43510B30BCD8F9AAF3E23E2DED42D150 /* Pods-needleAgent-umbrella.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 12B6D776DDD72E88CF71E998DFB14A71 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A78B1D34489977909D2AB901CD69FB56 /* Pods-needleAgentTests-umbrella.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 41F6279547BA8C6095CA882FB1059614 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ ACFE60AB34A7FEF697E6EEFB62436A8D /* CocoaAsyncSocket-umbrella.h in Headers */,
+ 6AAFA2F344A6B58F728AF958F3FE6D89 /* GCDAsyncSocket.h in Headers */,
+ 92AED971B91C640191A5ABA1567FE358 /* GCDAsyncUdpSocket.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 5A923E37DA33868284ADCB155823E15E /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74303F614D7B89EF02CD0A94B7AE653A /* Pods-needleAgentUITests-umbrella.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 59F5C74503FABF5A18CDC8F72EFE7F71 /* Pods-needleAgentUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 83A7664129B804E9A9B1110599ABE763 /* Build configuration list for PBXNativeTarget "Pods-needleAgentUITests" */;
+ buildPhases = (
+ D945AE4A4042F85CC9ED01C5B5CBF63E /* Sources */,
+ C604EE229AAC6913F3AFD92137C29616 /* Frameworks */,
+ 5A923E37DA33868284ADCB155823E15E /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Pods-needleAgentUITests";
+ productName = "Pods-needleAgentUITests";
+ productReference = 6B19FF960410818D4B37CF046246B95B /* Pods_needleAgentUITests.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ A32BEF1008CA1E940F9D3196DF512F17 /* Pods-needleAgentTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 46864B1CBC118CF8D337E401E2817197 /* Build configuration list for PBXNativeTarget "Pods-needleAgentTests" */;
+ buildPhases = (
+ 07E6A916433E83F57963002E384B2EA5 /* Sources */,
+ 1977D0BD1CC34A54660D4C46B1E983BA /* Frameworks */,
+ 12B6D776DDD72E88CF71E998DFB14A71 /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "Pods-needleAgentTests";
+ productName = "Pods-needleAgentTests";
+ productReference = EC1116D39076A6F6F02A66758AC9A1A3 /* Pods_needleAgentTests.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ B37E19CA872A7535725D1E1946E5C271 /* CocoaAsyncSocket */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 551479B287B69C7447DFBC6CA5EADF0B /* Build configuration list for PBXNativeTarget "CocoaAsyncSocket" */;
+ buildPhases = (
+ 217D9EA001DB8A7A00EBCC79A99280A3 /* Sources */,
+ B9ED45E2D8B8BCFB22AEA1A0810ABD96 /* Frameworks */,
+ 41F6279547BA8C6095CA882FB1059614 /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = CocoaAsyncSocket;
+ productName = CocoaAsyncSocket;
+ productReference = F9042062DDF6ABF8D82418E3CE687641 /* CocoaAsyncSocket.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ E915E7541DEABF1104CC9052C6A68362 /* Pods-needleAgent */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = D14F477A711C19B4481A59A5082989FD /* Build configuration list for PBXNativeTarget "Pods-needleAgent" */;
+ buildPhases = (
+ D12CC644264DBA8FCD5F81EBC1ADB154 /* Sources */,
+ F060F2D84868DD5152A5171CA7776D93 /* Frameworks */,
+ 10BCE19BF62229B9FEE320CE9994119A /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 1E713AB1A15CDD5EADF400F562B9BEF9 /* PBXTargetDependency */,
+ );
+ name = "Pods-needleAgent";
+ productName = "Pods-needleAgent";
+ productReference = B4833DA00A84017215D0266C73C46C03 /* Pods_needleAgent.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ D41D8CD98F00B204E9800998ECF8427E /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0730;
+ LastUpgradeCheck = 0700;
+ };
+ buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 7DB346D0F39D3F0E887471402A8071AB;
+ productRefGroup = F7AD0F2AB5904C988FD559BCDEFC9198 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ B37E19CA872A7535725D1E1946E5C271 /* CocoaAsyncSocket */,
+ E915E7541DEABF1104CC9052C6A68362 /* Pods-needleAgent */,
+ A32BEF1008CA1E940F9D3196DF512F17 /* Pods-needleAgentTests */,
+ 59F5C74503FABF5A18CDC8F72EFE7F71 /* Pods-needleAgentUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 07E6A916433E83F57963002E384B2EA5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 54314391F09C12F753B7DD806DF33521 /* Pods-needleAgentTests-dummy.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 217D9EA001DB8A7A00EBCC79A99280A3 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ F45B40DF0BF596BDCD96B2D85EF848FA /* CocoaAsyncSocket-dummy.m in Sources */,
+ 991BFD139F6A45C9B654960C2F6A7F09 /* GCDAsyncSocket.m in Sources */,
+ D9A44696356E299F3C9A4BFF9E57343F /* GCDAsyncUdpSocket.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ D12CC644264DBA8FCD5F81EBC1ADB154 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0AF7C5A1FC7A4EFDE8208E74E92E7AD4 /* Pods-needleAgent-dummy.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ D945AE4A4042F85CC9ED01C5B5CBF63E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ C920884F6B4223EE534F25AD1F5781C8 /* Pods-needleAgentUITests-dummy.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 1E713AB1A15CDD5EADF400F562B9BEF9 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = CocoaAsyncSocket;
+ target = B37E19CA872A7535725D1E1946E5C271 /* CocoaAsyncSocket */;
+ targetProxy = 03F6E93C16B3215F216609B09915880D /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 178F6A7787DE222202E61B587562B8D8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 394ED3AB63206DBD2D2446E72238A221 /* Pods-needleAgentTests.release.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgentTests/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.modulemap";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgentTests;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 2690931A3C2BFFDA04E2D3A7EE3ED85D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 13DFF73D1E951D4A997B3AD2A0FBD7E8 /* Pods-needleAgent.debug.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgent/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgent/Pods-needleAgent.modulemap";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgent;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 62F6EE431CF1688D2FCF1F1BE7213670 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DB8F994EDE49809A0295B78CCB5E797E /* Pods-needleAgent.release.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgent/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgent/Pods-needleAgent.modulemap";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgent;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 73AD7763A2163210E8D7897C7B9E7E33 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 169817E03CB1BCCBD39DEB030A241314 /* Pods-needleAgentTests.debug.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgentTests/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.modulemap";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgentTests;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 78F804CF4358E48070CD4A575F599B66 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 877C964C6B106941A9A62565894EC8F3 /* Pods-needleAgentUITests.debug.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgentUITests/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.modulemap";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgentUITests;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 7F6E181EAB24280412028ECCB8015D8F /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "POD_CONFIGURATION_RELEASE=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ STRIP_INSTALLED_PRODUCT = NO;
+ SYMROOT = "${SRCROOT}/../build";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 99BC16DAE09CDD5BA3C3A4F8E3D65225 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2EF436B09B560AF848B4D98C55F61FE0 /* CocoaAsyncSocket.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_PREFIX_HEADER = "Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-prefix.pch";
+ INFOPLIST_FILE = "Target Support Files/CocoaAsyncSocket/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MODULEMAP_FILE = "Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.modulemap";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_NAME = CocoaAsyncSocket;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ A19CEC7ABA92CFAB8DA687E5F2051AC0 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 293EA17C485A81E233632E9D73E42EDF /* Pods-needleAgentUITests.release.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = "Target Support Files/Pods-needleAgentUITests/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MACH_O_TYPE = staticlib;
+ MODULEMAP_FILE = "Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.modulemap";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOLFLAGS = "";
+ PODS_ROOT = "$(SRCROOT)";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = Pods_needleAgentUITests;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ A29D7B4850CDFED870E7CEBE7DAC0E63 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "POD_CONFIGURATION_DEBUG=1",
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ ONLY_ACTIVE_ARCH = YES;
+ STRIP_INSTALLED_PRODUCT = NO;
+ SYMROOT = "${SRCROOT}/../build";
+ };
+ name = Debug;
+ };
+ FE5DA8CB481D8B5F69FCAFE882F0C425 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 2EF436B09B560AF848B4D98C55F61FE0 /* CocoaAsyncSocket.xcconfig */;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_PREFIX_HEADER = "Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-prefix.pch";
+ INFOPLIST_FILE = "Target Support Files/CocoaAsyncSocket/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MODULEMAP_FILE = "Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.modulemap";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_NAME = CocoaAsyncSocket;
+ SDKROOT = iphoneos;
+ SKIP_INSTALL = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ A29D7B4850CDFED870E7CEBE7DAC0E63 /* Debug */,
+ 7F6E181EAB24280412028ECCB8015D8F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 46864B1CBC118CF8D337E401E2817197 /* Build configuration list for PBXNativeTarget "Pods-needleAgentTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 73AD7763A2163210E8D7897C7B9E7E33 /* Debug */,
+ 178F6A7787DE222202E61B587562B8D8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 551479B287B69C7447DFBC6CA5EADF0B /* Build configuration list for PBXNativeTarget "CocoaAsyncSocket" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 99BC16DAE09CDD5BA3C3A4F8E3D65225 /* Debug */,
+ FE5DA8CB481D8B5F69FCAFE882F0C425 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 83A7664129B804E9A9B1110599ABE763 /* Build configuration list for PBXNativeTarget "Pods-needleAgentUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 78F804CF4358E48070CD4A575F599B66 /* Debug */,
+ A19CEC7ABA92CFAB8DA687E5F2051AC0 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ D14F477A711C19B4481A59A5082989FD /* Build configuration list for PBXNativeTarget "Pods-needleAgent" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2690931A3C2BFFDA04E2D3A7EE3ED85D /* Debug */,
+ 62F6EE431CF1688D2FCF1F1BE7213670 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */;
+}
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-dummy.m b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-dummy.m
new file mode 100755
index 0000000..6b5b167
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_CocoaAsyncSocket : NSObject
+@end
+@implementation PodsDummy_CocoaAsyncSocket
+@end
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-prefix.pch b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-prefix.pch
new file mode 100755
index 0000000..aa992a4
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-prefix.pch
@@ -0,0 +1,4 @@
+#ifdef __OBJC__
+#import
+#endif
+
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-umbrella.h b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-umbrella.h
new file mode 100755
index 0000000..5a9b94d
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket-umbrella.h
@@ -0,0 +1,8 @@
+#import
+
+#import "GCDAsyncSocket.h"
+#import "GCDAsyncUdpSocket.h"
+
+FOUNDATION_EXPORT double CocoaAsyncSocketVersionNumber;
+FOUNDATION_EXPORT const unsigned char CocoaAsyncSocketVersionString[];
+
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.modulemap b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.modulemap
new file mode 100755
index 0000000..ec550b0
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.modulemap
@@ -0,0 +1,6 @@
+framework module CocoaAsyncSocket {
+ umbrella header "CocoaAsyncSocket-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.xcconfig b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.xcconfig
new file mode 100755
index 0000000..a2a9826
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/CocoaAsyncSocket.xcconfig
@@ -0,0 +1,9 @@
+CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
+OTHER_LDFLAGS = -framework "CFNetwork" -framework "Security"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
diff --git a/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/Info.plist b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/Info.plist
new file mode 100755
index 0000000..a68ea47
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/CocoaAsyncSocket/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ ${PRODUCT_BUNDLE_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 7.5.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${CURRENT_PROJECT_VERSION}
+ NSPrincipalClass
+
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Info.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Info.plist
new file mode 100755
index 0000000..2243fe6
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ ${PRODUCT_BUNDLE_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${CURRENT_PROJECT_VERSION}
+ NSPrincipalClass
+
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.markdown b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.markdown
new file mode 100755
index 0000000..8909bc0
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.markdown
@@ -0,0 +1,13 @@
+# Acknowledgements
+This application makes use of the following third party libraries:
+
+## CocoaAsyncSocket
+
+Public Domain License
+
+The CocoaAsyncSocket project is in the public domain.
+
+The original TCP version (AsyncSocket) was created by Dustin Voss in January 2003.
+Updated and maintained by Deusty LLC and the Apple development community.
+
+Generated by CocoaPods - https://cocoapods.org
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.plist
new file mode 100755
index 0000000..c215120
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-acknowledgements.plist
@@ -0,0 +1,43 @@
+
+
+
+
+ PreferenceSpecifiers
+
+
+ FooterText
+ This application makes use of the following third party libraries:
+ Title
+ Acknowledgements
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+ Public Domain License
+
+The CocoaAsyncSocket project is in the public domain.
+
+The original TCP version (AsyncSocket) was created by Dustin Voss in January 2003.
+Updated and maintained by Deusty LLC and the Apple development community.
+
+ Title
+ CocoaAsyncSocket
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+ Generated by CocoaPods - https://cocoapods.org
+ Title
+
+ Type
+ PSGroupSpecifier
+
+
+ StringsTable
+ Acknowledgements
+ Title
+ Acknowledgements
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-dummy.m b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-dummy.m
new file mode 100755
index 0000000..08ddbf7
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_Pods_needleAgent : NSObject
+@end
+@implementation PodsDummy_Pods_needleAgent
+@end
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-frameworks.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-frameworks.sh
new file mode 100755
index 0000000..2cfb978
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-frameworks.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+set -e
+
+echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
+
+install_framework()
+{
+ if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$1"
+ elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
+ elif [ -r "$1" ]; then
+ local source="$1"
+ fi
+
+ local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+ if [ -L "${source}" ]; then
+ echo "Symlinked..."
+ source="$(readlink "${source}")"
+ fi
+
+ # use filter instead of exclude so missing patterns dont' throw errors
+ echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
+ rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
+
+ local basename
+ basename="$(basename -s .framework "$1")"
+ binary="${destination}/${basename}.framework/${basename}"
+ if ! [ -r "$binary" ]; then
+ binary="${destination}/${basename}"
+ fi
+
+ # Strip invalid architectures so "fat" simulator / device frameworks work on device
+ if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
+ strip_invalid_archs "$binary"
+ fi
+
+ # Resign the code if required by the build settings to avoid unstable apps
+ code_sign_if_enabled "${destination}/$(basename "$1")"
+
+ # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
+ if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
+ local swift_runtime_libs
+ swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
+ for lib in $swift_runtime_libs; do
+ echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
+ rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
+ code_sign_if_enabled "${destination}/${lib}"
+ done
+ fi
+}
+
+# Signs a framework with the provided identity
+code_sign_if_enabled() {
+ if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
+ # Use the current code_sign_identitiy
+ echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
+ echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
+ /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
+ fi
+}
+
+# Strip invalid architectures
+strip_invalid_archs() {
+ binary="$1"
+ # Get architectures for current file
+ archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
+ stripped=""
+ for arch in $archs; do
+ if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
+ # Strip non-valid architectures in-place
+ lipo -remove "$arch" -output "$binary" "$binary" || exit 1
+ stripped="$stripped $arch"
+ fi
+ done
+ if [[ "$stripped" ]]; then
+ echo "Stripped $binary of architectures:$stripped"
+ fi
+}
+
+
+if [[ "$CONFIGURATION" == "Debug" ]]; then
+ install_framework "$BUILT_PRODUCTS_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework"
+fi
+if [[ "$CONFIGURATION" == "Release" ]]; then
+ install_framework "$BUILT_PRODUCTS_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework"
+fi
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-resources.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-resources.sh
new file mode 100755
index 0000000..0a15615
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-resources.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+set -e
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+
+RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
+> "$RESOURCES_TO_COPY"
+
+XCASSET_FILES=()
+
+case "${TARGETED_DEVICE_FAMILY}" in
+ 1,2)
+ TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
+ ;;
+ 1)
+ TARGET_DEVICE_ARGS="--target-device iphone"
+ ;;
+ 2)
+ TARGET_DEVICE_ARGS="--target-device ipad"
+ ;;
+ *)
+ TARGET_DEVICE_ARGS="--target-device mac"
+ ;;
+esac
+
+realpath() {
+ DIRECTORY="$(cd "${1%/*}" && pwd)"
+ FILENAME="${1##*/}"
+ echo "$DIRECTORY/$FILENAME"
+}
+
+install_resource()
+{
+ if [[ "$1" = /* ]] ; then
+ RESOURCE_PATH="$1"
+ else
+ RESOURCE_PATH="${PODS_ROOT}/$1"
+ fi
+ if [[ ! -e "$RESOURCE_PATH" ]] ; then
+ cat << EOM
+error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
+EOM
+ exit 1
+ fi
+ case $RESOURCE_PATH in
+ *.storyboard)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.xib)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.framework)
+ echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ ;;
+ *.xcdatamodel)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
+ ;;
+ *.xcdatamodeld)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
+ ;;
+ *.xcmappingmodel)
+ echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
+ xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
+ ;;
+ *.xcassets)
+ ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
+ XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
+ ;;
+ *)
+ echo "$RESOURCE_PATH"
+ echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
+ ;;
+ esac
+}
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
+ mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+ rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
+rm -f "$RESOURCES_TO_COPY"
+
+if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
+then
+ # Find all other xcassets (this unfortunately includes those of path pods and other targets).
+ OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
+ while read line; do
+ if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
+ XCASSET_FILES+=("$line")
+ fi
+ done <<<"$OTHER_XCASSETS"
+
+ printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-umbrella.h b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-umbrella.h
new file mode 100755
index 0000000..82d670c
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-umbrella.h
@@ -0,0 +1,6 @@
+#import
+
+
+FOUNDATION_EXPORT double Pods_needleAgentVersionNumber;
+FOUNDATION_EXPORT const unsigned char Pods_needleAgentVersionString[];
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.debug.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.debug.xcconfig
new file mode 100755
index 0000000..d0d825b
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.debug.xcconfig
@@ -0,0 +1,8 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+OTHER_LDFLAGS = $(inherited) -framework "CocoaAsyncSocket"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.modulemap b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.modulemap
new file mode 100755
index 0000000..1fe2e7b
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.modulemap
@@ -0,0 +1,6 @@
+framework module Pods_needleAgent {
+ umbrella header "Pods-needleAgent-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.release.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.release.xcconfig
new file mode 100755
index 0000000..d0d825b
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.release.xcconfig
@@ -0,0 +1,8 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+OTHER_LDFLAGS = $(inherited) -framework "CocoaAsyncSocket"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Info.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Info.plist
new file mode 100755
index 0000000..2243fe6
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ ${PRODUCT_BUNDLE_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${CURRENT_PROJECT_VERSION}
+ NSPrincipalClass
+
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.markdown b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.markdown
new file mode 100755
index 0000000..102af75
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.markdown
@@ -0,0 +1,3 @@
+# Acknowledgements
+This application makes use of the following third party libraries:
+Generated by CocoaPods - https://cocoapods.org
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.plist
new file mode 100755
index 0000000..7acbad1
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-acknowledgements.plist
@@ -0,0 +1,29 @@
+
+
+
+
+ PreferenceSpecifiers
+
+
+ FooterText
+ This application makes use of the following third party libraries:
+ Title
+ Acknowledgements
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+ Generated by CocoaPods - https://cocoapods.org
+ Title
+
+ Type
+ PSGroupSpecifier
+
+
+ StringsTable
+ Acknowledgements
+ Title
+ Acknowledgements
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-dummy.m b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-dummy.m
new file mode 100755
index 0000000..898256d
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_Pods_needleAgentTests : NSObject
+@end
+@implementation PodsDummy_Pods_needleAgentTests
+@end
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-frameworks.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-frameworks.sh
new file mode 100755
index 0000000..893c16a
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-frameworks.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+set -e
+
+echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
+
+install_framework()
+{
+ if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$1"
+ elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
+ elif [ -r "$1" ]; then
+ local source="$1"
+ fi
+
+ local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+ if [ -L "${source}" ]; then
+ echo "Symlinked..."
+ source="$(readlink "${source}")"
+ fi
+
+ # use filter instead of exclude so missing patterns dont' throw errors
+ echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
+ rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
+
+ local basename
+ basename="$(basename -s .framework "$1")"
+ binary="${destination}/${basename}.framework/${basename}"
+ if ! [ -r "$binary" ]; then
+ binary="${destination}/${basename}"
+ fi
+
+ # Strip invalid architectures so "fat" simulator / device frameworks work on device
+ if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
+ strip_invalid_archs "$binary"
+ fi
+
+ # Resign the code if required by the build settings to avoid unstable apps
+ code_sign_if_enabled "${destination}/$(basename "$1")"
+
+ # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
+ if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
+ local swift_runtime_libs
+ swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
+ for lib in $swift_runtime_libs; do
+ echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
+ rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
+ code_sign_if_enabled "${destination}/${lib}"
+ done
+ fi
+}
+
+# Signs a framework with the provided identity
+code_sign_if_enabled() {
+ if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
+ # Use the current code_sign_identitiy
+ echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
+ echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
+ /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
+ fi
+}
+
+# Strip invalid architectures
+strip_invalid_archs() {
+ binary="$1"
+ # Get architectures for current file
+ archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
+ stripped=""
+ for arch in $archs; do
+ if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
+ # Strip non-valid architectures in-place
+ lipo -remove "$arch" -output "$binary" "$binary" || exit 1
+ stripped="$stripped $arch"
+ fi
+ done
+ if [[ "$stripped" ]]; then
+ echo "Stripped $binary of architectures:$stripped"
+ fi
+}
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-resources.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-resources.sh
new file mode 100755
index 0000000..0a15615
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-resources.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+set -e
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+
+RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
+> "$RESOURCES_TO_COPY"
+
+XCASSET_FILES=()
+
+case "${TARGETED_DEVICE_FAMILY}" in
+ 1,2)
+ TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
+ ;;
+ 1)
+ TARGET_DEVICE_ARGS="--target-device iphone"
+ ;;
+ 2)
+ TARGET_DEVICE_ARGS="--target-device ipad"
+ ;;
+ *)
+ TARGET_DEVICE_ARGS="--target-device mac"
+ ;;
+esac
+
+realpath() {
+ DIRECTORY="$(cd "${1%/*}" && pwd)"
+ FILENAME="${1##*/}"
+ echo "$DIRECTORY/$FILENAME"
+}
+
+install_resource()
+{
+ if [[ "$1" = /* ]] ; then
+ RESOURCE_PATH="$1"
+ else
+ RESOURCE_PATH="${PODS_ROOT}/$1"
+ fi
+ if [[ ! -e "$RESOURCE_PATH" ]] ; then
+ cat << EOM
+error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
+EOM
+ exit 1
+ fi
+ case $RESOURCE_PATH in
+ *.storyboard)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.xib)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.framework)
+ echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ ;;
+ *.xcdatamodel)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
+ ;;
+ *.xcdatamodeld)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
+ ;;
+ *.xcmappingmodel)
+ echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
+ xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
+ ;;
+ *.xcassets)
+ ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
+ XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
+ ;;
+ *)
+ echo "$RESOURCE_PATH"
+ echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
+ ;;
+ esac
+}
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
+ mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+ rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
+rm -f "$RESOURCES_TO_COPY"
+
+if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
+then
+ # Find all other xcassets (this unfortunately includes those of path pods and other targets).
+ OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
+ while read line; do
+ if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
+ XCASSET_FILES+=("$line")
+ fi
+ done <<<"$OTHER_XCASSETS"
+
+ printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-umbrella.h b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-umbrella.h
new file mode 100755
index 0000000..a4fc321
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests-umbrella.h
@@ -0,0 +1,6 @@
+#import
+
+
+FOUNDATION_EXPORT double Pods_needleAgentTestsVersionNumber;
+FOUNDATION_EXPORT const unsigned char Pods_needleAgentTestsVersionString[];
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.debug.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.debug.xcconfig
new file mode 100755
index 0000000..5746816
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.debug.xcconfig
@@ -0,0 +1,7 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.modulemap b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.modulemap
new file mode 100755
index 0000000..22254dd
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.modulemap
@@ -0,0 +1,6 @@
+framework module Pods_needleAgentTests {
+ umbrella header "Pods-needleAgentTests-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.release.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.release.xcconfig
new file mode 100755
index 0000000..5746816
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.release.xcconfig
@@ -0,0 +1,7 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Info.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Info.plist
new file mode 100755
index 0000000..2243fe6
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIdentifier
+ ${PRODUCT_BUNDLE_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${CURRENT_PROJECT_VERSION}
+ NSPrincipalClass
+
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.markdown b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.markdown
new file mode 100755
index 0000000..102af75
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.markdown
@@ -0,0 +1,3 @@
+# Acknowledgements
+This application makes use of the following third party libraries:
+Generated by CocoaPods - https://cocoapods.org
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.plist b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.plist
new file mode 100755
index 0000000..7acbad1
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-acknowledgements.plist
@@ -0,0 +1,29 @@
+
+
+
+
+ PreferenceSpecifiers
+
+
+ FooterText
+ This application makes use of the following third party libraries:
+ Title
+ Acknowledgements
+ Type
+ PSGroupSpecifier
+
+
+ FooterText
+ Generated by CocoaPods - https://cocoapods.org
+ Title
+
+ Type
+ PSGroupSpecifier
+
+
+ StringsTable
+ Acknowledgements
+ Title
+ Acknowledgements
+
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-dummy.m b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-dummy.m
new file mode 100755
index 0000000..b464be4
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-dummy.m
@@ -0,0 +1,5 @@
+#import
+@interface PodsDummy_Pods_needleAgentUITests : NSObject
+@end
+@implementation PodsDummy_Pods_needleAgentUITests
+@end
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-frameworks.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-frameworks.sh
new file mode 100755
index 0000000..893c16a
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-frameworks.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+set -e
+
+echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
+
+install_framework()
+{
+ if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$1"
+ elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
+ local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
+ elif [ -r "$1" ]; then
+ local source="$1"
+ fi
+
+ local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+
+ if [ -L "${source}" ]; then
+ echo "Symlinked..."
+ source="$(readlink "${source}")"
+ fi
+
+ # use filter instead of exclude so missing patterns dont' throw errors
+ echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
+ rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
+
+ local basename
+ basename="$(basename -s .framework "$1")"
+ binary="${destination}/${basename}.framework/${basename}"
+ if ! [ -r "$binary" ]; then
+ binary="${destination}/${basename}"
+ fi
+
+ # Strip invalid architectures so "fat" simulator / device frameworks work on device
+ if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
+ strip_invalid_archs "$binary"
+ fi
+
+ # Resign the code if required by the build settings to avoid unstable apps
+ code_sign_if_enabled "${destination}/$(basename "$1")"
+
+ # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
+ if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
+ local swift_runtime_libs
+ swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
+ for lib in $swift_runtime_libs; do
+ echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
+ rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
+ code_sign_if_enabled "${destination}/${lib}"
+ done
+ fi
+}
+
+# Signs a framework with the provided identity
+code_sign_if_enabled() {
+ if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
+ # Use the current code_sign_identitiy
+ echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
+ echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements \"$1\""
+ /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements "$1"
+ fi
+}
+
+# Strip invalid architectures
+strip_invalid_archs() {
+ binary="$1"
+ # Get architectures for current file
+ archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
+ stripped=""
+ for arch in $archs; do
+ if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
+ # Strip non-valid architectures in-place
+ lipo -remove "$arch" -output "$binary" "$binary" || exit 1
+ stripped="$stripped $arch"
+ fi
+ done
+ if [[ "$stripped" ]]; then
+ echo "Stripped $binary of architectures:$stripped"
+ fi
+}
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-resources.sh b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-resources.sh
new file mode 100755
index 0000000..0a15615
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-resources.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+set -e
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+
+RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
+> "$RESOURCES_TO_COPY"
+
+XCASSET_FILES=()
+
+case "${TARGETED_DEVICE_FAMILY}" in
+ 1,2)
+ TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
+ ;;
+ 1)
+ TARGET_DEVICE_ARGS="--target-device iphone"
+ ;;
+ 2)
+ TARGET_DEVICE_ARGS="--target-device ipad"
+ ;;
+ *)
+ TARGET_DEVICE_ARGS="--target-device mac"
+ ;;
+esac
+
+realpath() {
+ DIRECTORY="$(cd "${1%/*}" && pwd)"
+ FILENAME="${1##*/}"
+ echo "$DIRECTORY/$FILENAME"
+}
+
+install_resource()
+{
+ if [[ "$1" = /* ]] ; then
+ RESOURCE_PATH="$1"
+ else
+ RESOURCE_PATH="${PODS_ROOT}/$1"
+ fi
+ if [[ ! -e "$RESOURCE_PATH" ]] ; then
+ cat << EOM
+error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
+EOM
+ exit 1
+ fi
+ case $RESOURCE_PATH in
+ *.storyboard)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.xib)
+ echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
+ ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
+ ;;
+ *.framework)
+ echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
+ ;;
+ *.xcdatamodel)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
+ ;;
+ *.xcdatamodeld)
+ echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
+ xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
+ ;;
+ *.xcmappingmodel)
+ echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
+ xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
+ ;;
+ *.xcassets)
+ ABSOLUTE_XCASSET_FILE=$(realpath "$RESOURCE_PATH")
+ XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
+ ;;
+ *)
+ echo "$RESOURCE_PATH"
+ echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
+ ;;
+ esac
+}
+
+mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
+ mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+ rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
+rm -f "$RESOURCES_TO_COPY"
+
+if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
+then
+ # Find all other xcassets (this unfortunately includes those of path pods and other targets).
+ OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
+ while read line; do
+ if [[ $line != "`realpath $PODS_ROOT`*" ]]; then
+ XCASSET_FILES+=("$line")
+ fi
+ done <<<"$OTHER_XCASSETS"
+
+ printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
+fi
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-umbrella.h b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-umbrella.h
new file mode 100755
index 0000000..a0a9314
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests-umbrella.h
@@ -0,0 +1,6 @@
+#import
+
+
+FOUNDATION_EXPORT double Pods_needleAgentUITestsVersionNumber;
+FOUNDATION_EXPORT const unsigned char Pods_needleAgentUITestsVersionString[];
+
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.debug.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.debug.xcconfig
new file mode 100755
index 0000000..5746816
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.debug.xcconfig
@@ -0,0 +1,7 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.modulemap b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.modulemap
new file mode 100755
index 0000000..ea150ef
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.modulemap
@@ -0,0 +1,6 @@
+framework module Pods_needleAgentUITests {
+ umbrella header "Pods-needleAgentUITests-umbrella.h"
+
+ export *
+ module * { export * }
+}
diff --git a/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.release.xcconfig b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.release.xcconfig
new file mode 100755
index 0000000..5746816
--- /dev/null
+++ b/needle-agent/Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.release.xcconfig
@@ -0,0 +1,7 @@
+FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket"
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
+OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/CocoaAsyncSocket/CocoaAsyncSocket.framework/Headers"
+PODS_BUILD_DIR = $BUILD_DIR
+PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}/Pods
diff --git a/needle-agent/needleAgent.xcodeproj/project.pbxproj b/needle-agent/needleAgent.xcodeproj/project.pbxproj
new file mode 100755
index 0000000..e6b5295
--- /dev/null
+++ b/needle-agent/needleAgent.xcodeproj/project.pbxproj
@@ -0,0 +1,537 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 518B75850D12DD83877A4729 /* Pods_needleAgent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B29985A24DBA4B98B3862D2 /* Pods_needleAgent.framework */; };
+ A90CB6A71E6727AA009FEA35 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A90CB6A61E6727A2009FEA35 /* Security.framework */; };
+ A974E21B1E685B4100F60BEF /* OpcodeProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = A93B0E0A1E157C0C002AAAA8 /* OpcodeProtocol.h */; };
+ A974E21E1E68608D00F60BEF /* OpcodeSTOP.m in Sources */ = {isa = PBXBuildFile; fileRef = A974E21D1E68608D00F60BEF /* OpcodeSTOP.m */; };
+ A974E2201E6861A500F60BEF /* OpcodeLIST_APPS.m in Sources */ = {isa = PBXBuildFile; fileRef = A974E21F1E6861A500F60BEF /* OpcodeLIST_APPS.m */; };
+ A974E2221E68732600F60BEF /* OpcodeOS_VERSION.m in Sources */ = {isa = PBXBuildFile; fileRef = A974E2211E68732600F60BEF /* OpcodeOS_VERSION.m */; };
+ A9A27A4F1E5DD04B0014E1A7 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9A27A4E1E5DD04B0014E1A7 /* MobileCoreServices.framework */; };
+ A9A27A521E5DD1350014E1A7 /* FBApplicationInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = A9A27A501E5DD1350014E1A7 /* FBApplicationInfo.h */; };
+ A9A27A531E5DD1350014E1A7 /* LSApplicationProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = A9A27A511E5DD1350014E1A7 /* LSApplicationProxy.h */; };
+ A9C7110D1E65DD4D0084103A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9C7110C1E65DD4D0084103A /* Assets.xcassets */; };
+ A9C7110F1E65E8DD0084103A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C7110E1E65E8D60084103A /* Foundation.framework */; };
+ A9CFE9511E5DA69B006D6E08 /* LSApplicationWorkspace.h in Headers */ = {isa = PBXBuildFile; fileRef = A9CFE9501E5DA69B006D6E08 /* LSApplicationWorkspace.h */; };
+ E912BB4D1D59EA1100B8E165 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E912BB4C1D59EA1100B8E165 /* Main.storyboard */; };
+ E91B6FD21D587B44000A2939 /* MWRServerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E91B6FD01D587B44000A2939 /* MWRServerViewController.m */; };
+ E91E39241D5B4BC600720D62 /* MWRCommandHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E91E39231D5B4BC600720D62 /* MWRCommandHandler.m */; };
+ E91E39331D66094B00720D62 /* MWRAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = E91E39321D66094B00720D62 /* MWRAsyncSocket.m */; };
+ E9B171F71D548F6A00DB3B60 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B171F61D548F6A00DB3B60 /* main.m */; };
+ E9B171FA1D548F6A00DB3B60 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B171F91D548F6A00DB3B60 /* AppDelegate.m */; };
+ E9B171FF1D548F6A00DB3B60 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9B171FD1D548F6A00DB3B60 /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 00A227A1A7CADB052076DAC1 /* Pods-needleAgent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgent.release.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.release.xcconfig"; sourceTree = ""; };
+ 0B29985A24DBA4B98B3862D2 /* Pods_needleAgent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_needleAgent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 305856903DDB45CA50AE55C2 /* Pods-needleAgentTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgentTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.release.xcconfig"; sourceTree = ""; };
+ 7B014009855E07263F36B776 /* Pods-needleAgentUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgentUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.release.xcconfig"; sourceTree = ""; };
+ 83310C1D77ED75BE61B9DEB9 /* Pods-needleAgentTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgentTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgentTests/Pods-needleAgentTests.debug.xcconfig"; sourceTree = ""; };
+ A90CB6A61E6727A2009FEA35 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+ A90CB6A91E6728DE009FEA35 /* SecurityFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityFoundation.framework; path = ../../../../../../../../System/Library/Frameworks/SecurityFoundation.framework; sourceTree = ""; };
+ A90CB6AB1E672907009FEA35 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = ../../../../../../../../System/Library/Frameworks/Cocoa.framework; sourceTree = ""; };
+ A93B0E0A1E157C0C002AAAA8 /* OpcodeProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OpcodeProtocol.h; sourceTree = ""; };
+ A974E21D1E68608D00F60BEF /* OpcodeSTOP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpcodeSTOP.m; sourceTree = ""; };
+ A974E21F1E6861A500F60BEF /* OpcodeLIST_APPS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpcodeLIST_APPS.m; sourceTree = ""; };
+ A974E2211E68732600F60BEF /* OpcodeOS_VERSION.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OpcodeOS_VERSION.m; sourceTree = ""; };
+ A99C83CA1E14564600D7D13C /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; };
+ A9A27A4E1E5DD04B0014E1A7 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
+ A9A27A501E5DD1350014E1A7 /* FBApplicationInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBApplicationInfo.h; sourceTree = ""; };
+ A9A27A511E5DD1350014E1A7 /* LSApplicationProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LSApplicationProxy.h; sourceTree = ""; };
+ A9C7110C1E65DD4D0084103A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ A9C7110E1E65E8D60084103A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ A9CEF2D81E607515006A827E /* Xcode.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = Xcode.app; path = ../../../../../../../../Applications/Xcode.app; sourceTree = ""; };
+ A9CEF2DA1E607588006A827E /* SpringBoardServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpringBoardServices.framework; path = Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/SpringBoardServices.framework; sourceTree = DEVELOPER_DIR; };
+ A9CFE9501E5DA69B006D6E08 /* LSApplicationWorkspace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LSApplicationWorkspace.h; sourceTree = ""; };
+ D4424905D46E85C419630E9B /* Pods-needleAgent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgent.debug.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent.debug.xcconfig"; sourceTree = ""; };
+ E912BB4C1D59EA1100B8E165 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
+ E91B6FCF1D587B44000A2939 /* MWRServerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWRServerViewController.h; sourceTree = ""; };
+ E91B6FD01D587B44000A2939 /* MWRServerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWRServerViewController.m; sourceTree = ""; };
+ E91E39221D5B4BC600720D62 /* MWRCommandHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWRCommandHandler.h; sourceTree = ""; };
+ E91E39231D5B4BC600720D62 /* MWRCommandHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWRCommandHandler.m; sourceTree = ""; };
+ E91E39311D66094B00720D62 /* MWRAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWRAsyncSocket.h; sourceTree = ""; };
+ E91E39321D66094B00720D62 /* MWRAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWRAsyncSocket.m; sourceTree = ""; };
+ E9B171F21D548F6A00DB3B60 /* needleAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = needleAgent.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ E9B171F61D548F6A00DB3B60 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ E9B171F81D548F6A00DB3B60 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ E9B171F91D548F6A00DB3B60 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ E9B171FE1D548F6A00DB3B60 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ E9B172001D548F6A00DB3B60 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ FC34ACBEC7EFC5D04C31FD72 /* Pods-needleAgentUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-needleAgentUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-needleAgentUITests/Pods-needleAgentUITests.debug.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ E9B171EF1D548F6A00DB3B60 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A90CB6A71E6727AA009FEA35 /* Security.framework in Frameworks */,
+ A9C7110F1E65E8DD0084103A /* Foundation.framework in Frameworks */,
+ A9A27A4F1E5DD04B0014E1A7 /* MobileCoreServices.framework in Frameworks */,
+ 518B75850D12DD83877A4729 /* Pods_needleAgent.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 071283659E8D6E515A78748C /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ D4424905D46E85C419630E9B /* Pods-needleAgent.debug.xcconfig */,
+ 00A227A1A7CADB052076DAC1 /* Pods-needleAgent.release.xcconfig */,
+ 83310C1D77ED75BE61B9DEB9 /* Pods-needleAgentTests.debug.xcconfig */,
+ 305856903DDB45CA50AE55C2 /* Pods-needleAgentTests.release.xcconfig */,
+ FC34ACBEC7EFC5D04C31FD72 /* Pods-needleAgentUITests.debug.xcconfig */,
+ 7B014009855E07263F36B776 /* Pods-needleAgentUITests.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "";
+ };
+ 19C2791FA896417D16D6BDA4 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ A90CB6AB1E672907009FEA35 /* Cocoa.framework */,
+ A90CB6A91E6728DE009FEA35 /* SecurityFoundation.framework */,
+ A90CB6A61E6727A2009FEA35 /* Security.framework */,
+ A9C7110E1E65E8D60084103A /* Foundation.framework */,
+ A9CEF2DA1E607588006A827E /* SpringBoardServices.framework */,
+ A9CEF2D81E607515006A827E /* Xcode.app */,
+ A9A27A4E1E5DD04B0014E1A7 /* MobileCoreServices.framework */,
+ 0B29985A24DBA4B98B3862D2 /* Pods_needleAgent.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ A90CB6A11E66EA71009FEA35 /* PrivateInterfaces */ = {
+ isa = PBXGroup;
+ children = (
+ A9A27A501E5DD1350014E1A7 /* FBApplicationInfo.h */,
+ A9A27A511E5DD1350014E1A7 /* LSApplicationProxy.h */,
+ A9CFE9501E5DA69B006D6E08 /* LSApplicationWorkspace.h */,
+ );
+ name = PrivateInterfaces;
+ sourceTree = "";
+ };
+ A974E2191E68570D00F60BEF /* Server */ = {
+ isa = PBXGroup;
+ children = (
+ E91B6FCF1D587B44000A2939 /* MWRServerViewController.h */,
+ E91B6FD01D587B44000A2939 /* MWRServerViewController.m */,
+ E91E39311D66094B00720D62 /* MWRAsyncSocket.h */,
+ E91E39321D66094B00720D62 /* MWRAsyncSocket.m */,
+ );
+ name = Server;
+ sourceTree = "";
+ };
+ A99C83C81E14515300D7D13C /* Opcodes */ = {
+ isa = PBXGroup;
+ children = (
+ A90CB6A11E66EA71009FEA35 /* PrivateInterfaces */,
+ A93B0E0A1E157C0C002AAAA8 /* OpcodeProtocol.h */,
+ A974E21D1E68608D00F60BEF /* OpcodeSTOP.m */,
+ A974E21F1E6861A500F60BEF /* OpcodeLIST_APPS.m */,
+ A974E2211E68732600F60BEF /* OpcodeOS_VERSION.m */,
+ );
+ name = Opcodes;
+ sourceTree = "";
+ };
+ A99C83C91E14528B00D7D13C /* Handlers */ = {
+ isa = PBXGroup;
+ children = (
+ E91E39221D5B4BC600720D62 /* MWRCommandHandler.h */,
+ E91E39231D5B4BC600720D62 /* MWRCommandHandler.m */,
+ );
+ name = Handlers;
+ sourceTree = "";
+ };
+ E9B171E91D548F6A00DB3B60 = {
+ isa = PBXGroup;
+ children = (
+ E9B171F41D548F6A00DB3B60 /* needleAgent */,
+ E9B171F31D548F6A00DB3B60 /* Products */,
+ 071283659E8D6E515A78748C /* Pods */,
+ 19C2791FA896417D16D6BDA4 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ E9B171F31D548F6A00DB3B60 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ E9B171F21D548F6A00DB3B60 /* needleAgent.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ E9B171F41D548F6A00DB3B60 /* needleAgent */ = {
+ isa = PBXGroup;
+ children = (
+ A974E2191E68570D00F60BEF /* Server */,
+ A99C83C91E14528B00D7D13C /* Handlers */,
+ A99C83C81E14515300D7D13C /* Opcodes */,
+ E9B171F81D548F6A00DB3B60 /* AppDelegate.h */,
+ E9B171F91D548F6A00DB3B60 /* AppDelegate.m */,
+ A99C83CA1E14564600D7D13C /* Constants.h */,
+ E9B171FD1D548F6A00DB3B60 /* LaunchScreen.storyboard */,
+ E9B172001D548F6A00DB3B60 /* Info.plist */,
+ E9B171F51D548F6A00DB3B60 /* Supporting Files */,
+ E912BB4C1D59EA1100B8E165 /* Main.storyboard */,
+ A9C7110C1E65DD4D0084103A /* Assets.xcassets */,
+ );
+ path = needleAgent;
+ sourceTree = "";
+ };
+ E9B171F51D548F6A00DB3B60 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ E9B171F61D548F6A00DB3B60 /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ A9E467691E5CA0E700C476A6 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A9A27A531E5DD1350014E1A7 /* LSApplicationProxy.h in Headers */,
+ A9CFE9511E5DA69B006D6E08 /* LSApplicationWorkspace.h in Headers */,
+ A974E21B1E685B4100F60BEF /* OpcodeProtocol.h in Headers */,
+ A9A27A521E5DD1350014E1A7 /* FBApplicationInfo.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ E9B171F11D548F6A00DB3B60 /* needleAgent */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = E9B172191D548F6A00DB3B60 /* Build configuration list for PBXNativeTarget "needleAgent" */;
+ buildPhases = (
+ A9C74BCE29E5925509B6534F /* [CP] Check Pods Manifest.lock */,
+ E9B171EE1D548F6A00DB3B60 /* Sources */,
+ E9B171EF1D548F6A00DB3B60 /* Frameworks */,
+ E9B171F01D548F6A00DB3B60 /* Resources */,
+ F982E4A6122CD11409D8925F /* [CP] Embed Pods Frameworks */,
+ F53F69FA7FEF8639E4748DC9 /* [CP] Copy Pods Resources */,
+ A9E467691E5CA0E700C476A6 /* Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = needleAgent;
+ productName = needleAgent;
+ productReference = E9B171F21D548F6A00DB3B60 /* needleAgent.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ E9B171EA1D548F6A00DB3B60 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0730;
+ ORGANIZATIONNAME = MWR;
+ TargetAttributes = {
+ E9B171F11D548F6A00DB3B60 = {
+ CreatedOnToolsVersion = 7.3.1;
+ DevelopmentTeam = U38MMB3T7J;
+ ProvisioningStyle = Automatic;
+ SystemCapabilities = {
+ com.apple.BackgroundModes = {
+ enabled = 1;
+ };
+ com.apple.Keychain = {
+ enabled = 1;
+ };
+ };
+ };
+ };
+ };
+ buildConfigurationList = E9B171ED1D548F6A00DB3B60 /* Build configuration list for PBXProject "needleAgent" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = E9B171E91D548F6A00DB3B60;
+ productRefGroup = E9B171F31D548F6A00DB3B60 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ E9B171F11D548F6A00DB3B60 /* needleAgent */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ E9B171F01D548F6A00DB3B60 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E9B171FF1D548F6A00DB3B60 /* LaunchScreen.storyboard in Resources */,
+ A9C7110D1E65DD4D0084103A /* Assets.xcassets in Resources */,
+ E912BB4D1D59EA1100B8E165 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ A9C74BCE29E5925509B6534F /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ F53F69FA7FEF8639E4748DC9 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ F982E4A6122CD11409D8925F /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-needleAgent/Pods-needleAgent-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ E9B171EE1D548F6A00DB3B60 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E9B171FA1D548F6A00DB3B60 /* AppDelegate.m in Sources */,
+ E91E39331D66094B00720D62 /* MWRAsyncSocket.m in Sources */,
+ E91B6FD21D587B44000A2939 /* MWRServerViewController.m in Sources */,
+ E9B171F71D548F6A00DB3B60 /* main.m in Sources */,
+ A974E2201E6861A500F60BEF /* OpcodeLIST_APPS.m in Sources */,
+ E91E39241D5B4BC600720D62 /* MWRCommandHandler.m in Sources */,
+ A974E2221E68732600F60BEF /* OpcodeOS_VERSION.m in Sources */,
+ A974E21E1E68608D00F60BEF /* OpcodeSTOP.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ E9B171FD1D548F6A00DB3B60 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ E9B171FE1D548F6A00DB3B60 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ E9B172171D548F6A00DB3B60 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = YES;
+ ARCHS = "$(ARCHS_STANDARD)";
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PROVISIONING_PROFILE = "";
+ SDKROOT = iphoneos10.2;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALID_ARCHS = "arm64 armv7";
+ };
+ name = Debug;
+ };
+ E9B172181D548F6A00DB3B60 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = YES;
+ ARCHS = "$(ARCHS_STANDARD)";
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PROVISIONING_PROFILE = "";
+ SDKROOT = iphoneos10.2;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ VALID_ARCHS = "arm64 armv7";
+ };
+ name = Release;
+ };
+ E9B1721A1D548F6A00DB3B60 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = D4424905D46E85C419630E9B /* Pods-needleAgent.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_CXX_LIBRARY = "libstdc++";
+ CODE_SIGN_ENTITLEMENTS = "";
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ DEVELOPMENT_TEAM = U38MMB3T7J;
+ ENABLE_ONLY_ACTIVE_RESOURCES = YES;
+ INFOPLIST_FILE = needleAgent/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ ONLY_ACTIVE_ARCH = YES;
+ "OTHER_CODE_SIGN_FLAGS[sdk=*]" = "";
+ PRODUCT_BUNDLE_IDENTIFIER = mwr.needle.agent;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALID_ARCHS = "arm64 armv7";
+ };
+ name = Debug;
+ };
+ E9B1721B1D548F6A00DB3B60 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 00A227A1A7CADB052076DAC1 /* Pods-needleAgent.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_CXX_LIBRARY = "libstdc++";
+ CODE_SIGN_ENTITLEMENTS = "";
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ DEVELOPMENT_TEAM = U38MMB3T7J;
+ ENABLE_ONLY_ACTIVE_RESOURCES = YES;
+ INFOPLIST_FILE = needleAgent/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = mwr.needle.agent;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALID_ARCHS = "arm64 armv7";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ E9B171ED1D548F6A00DB3B60 /* Build configuration list for PBXProject "needleAgent" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ E9B172171D548F6A00DB3B60 /* Debug */,
+ E9B172181D548F6A00DB3B60 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ E9B172191D548F6A00DB3B60 /* Build configuration list for PBXNativeTarget "needleAgent" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ E9B1721A1D548F6A00DB3B60 /* Debug */,
+ E9B1721B1D548F6A00DB3B60 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = E9B171EA1D548F6A00DB3B60 /* Project object */;
+}
diff --git a/needle-agent/needleAgent.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/needle-agent/needleAgent.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100755
index 0000000..7270ac4
--- /dev/null
+++ b/needle-agent/needleAgent.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/needle-agent/needleAgent.xcworkspace/contents.xcworkspacedata b/needle-agent/needleAgent.xcworkspace/contents.xcworkspacedata
new file mode 100755
index 0000000..26a3fc9
--- /dev/null
+++ b/needle-agent/needleAgent.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/needle-agent/needleAgent/AppDelegate.h b/needle-agent/needleAgent/AppDelegate.h
new file mode 100755
index 0000000..d67b2c7
--- /dev/null
+++ b/needle-agent/needleAgent/AppDelegate.h
@@ -0,0 +1,13 @@
+//
+// AppDelegate.h
+// needleAgent
+//
+
+#import
+
+@interface AppDelegate : UIResponder
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
+
diff --git a/needle-agent/needleAgent/AppDelegate.m b/needle-agent/needleAgent/AppDelegate.m
new file mode 100755
index 0000000..19f52ad
--- /dev/null
+++ b/needle-agent/needleAgent/AppDelegate.m
@@ -0,0 +1,18 @@
+//
+// AppDelegate.m
+// needleAgent
+//
+
+#import "AppDelegate.h"
+#import "MWRServerViewController.h"
+
+@interface AppDelegate ()
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ return YES;
+}
+
+@end
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Contents.json b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..6d4a683
--- /dev/null
+++ b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,142 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "57x57",
+ "scale" : "1x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x-1.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x-1.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x-1.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "needle_logo_vis 12 copy-4.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..8c2d2ba
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png
new file mode 100644
index 0000000..7b83801
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..7b83801
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..85da910
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..de80b96
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..5bdc7d6
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x-1.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x-1.png
new file mode 100644
index 0000000..f3fd4e8
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x-1.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..f3fd4e8
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..180142e
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x-1.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x-1.png
new file mode 100644
index 0000000..d7d6213
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x-1.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..d7d6213
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x-1.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x-1.png
new file mode 100644
index 0000000..4836ea8
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x-1.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..4836ea8
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/needle_logo_vis 12 copy-4.png b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/needle_logo_vis 12 copy-4.png
new file mode 100644
index 0000000..56a3a9f
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/AppIcon.appiconset/needle_logo_vis 12 copy-4.png differ
diff --git a/needle-agent/needleAgent/Assets.xcassets/Contents.json b/needle-agent/needleAgent/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/needle-agent/needleAgent/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/Contents.json b/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/Contents.json
new file mode 100644
index 0000000..0c3f60b
--- /dev/null
+++ b/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "needle_primary_pos_rgb.jpg",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/needle_primary_pos_rgb.jpg b/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/needle_primary_pos_rgb.jpg
new file mode 100644
index 0000000..a9f78c5
Binary files /dev/null and b/needle-agent/needleAgent/Assets.xcassets/Logo.imageset/needle_primary_pos_rgb.jpg differ
diff --git a/needle-agent/needleAgent/Base.lproj/LaunchScreen.storyboard b/needle-agent/needleAgent/Base.lproj/LaunchScreen.storyboard
new file mode 100755
index 0000000..1709aef
--- /dev/null
+++ b/needle-agent/needleAgent/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/needle-agent/needleAgent/Constants.h b/needle-agent/needleAgent/Constants.h
new file mode 100644
index 0000000..78e9649
--- /dev/null
+++ b/needle-agent/needleAgent/Constants.h
@@ -0,0 +1,20 @@
+//
+// Constants.h
+// needleAgent
+//
+
+#ifndef Constants_h
+#define Constants_h
+
+#define AGENT_VERSION @"1.0"
+#define DEFAULT_PORT @"4444"
+
+#define AGENT_NAME @"Needle Agent"
+#define AGENT_WELCOME @"Welcome to Needle Agent"
+
+#define COMMAND_DISCONNECT @"Disconnect"
+#define COMMAND_RETURN @"RETURN"
+#define COMMAND_VERSION @"VERSION"
+
+
+#endif
diff --git a/needle-agent/needleAgent/FBApplicationInfo.h b/needle-agent/needleAgent/FBApplicationInfo.h
new file mode 100644
index 0000000..9182d2c
--- /dev/null
+++ b/needle-agent/needleAgent/FBApplicationInfo.h
@@ -0,0 +1,110 @@
+#import
+
+@interface FBBundleInfo : NSObject
+{
+ id _proxy;
+ NSString *_displayName;
+ NSString *_bundleIdentifier;
+ NSString *_bundleVersion;
+ NSString *_bundleType;
+ NSURL *_bundleURL;
+}
+
+@property(retain, nonatomic) NSURL *bundleURL; // @synthesize bundleURL=_bundleURL;
+@property(copy, nonatomic) NSString *bundleType; // @synthesize bundleType=_bundleType;
+@property(copy, nonatomic) NSString *bundleVersion; // @synthesize bundleVersion=_bundleVersion;
+@property(copy, nonatomic) NSString *bundleIdentifier; // @synthesize bundleIdentifier=_bundleIdentifier;
+@property(copy, nonatomic) NSString *displayName; // @synthesize displayName=_displayName;
+@property(readonly, retain, nonatomic, getter=_proxy) id proxy; // @synthesize proxy=_proxy;
+
+- (id)initWithApplicationProxy:(id)arg1;
+
+@end
+
+@interface FBApplicationInfo : FBBundleInfo
+{
+ NSURL *_executableURL;
+ NSURL *_bundleContainerURL;
+ NSURL *_dataContainerURL;
+ NSURL *_sandboxURL;
+ double _lastModifiedDate;
+ NSString *_preferenceDomain;
+ NSString *_signerIdentity;
+ NSDictionary *_environmentVariables;
+ NSDictionary *_entitlements;
+ _Bool _provisioningProfileValidated;
+ NSString *_sdkVersion;
+ NSArray *_customMachServices;
+ unsigned long long _type;
+ NSArray *_requiredCapabilities;
+ NSArray *_tags;
+ NSArray *_deviceFamilies;
+ _Bool _enabled;
+ _Bool _newsstand;
+ _Bool _restricted;
+ _Bool _beta;
+ NSSet *_backgroundModes;
+ NSSet *_supportedInterfaceOrientations;
+ _Bool _exitsOnSuspend;
+ _Bool _requiresPersistentWiFi;
+ float _minimumBrightnessLevel;
+ NSArray *_externalAccessoryProtocols;
+ long long _ratingRank;
+ NSArray *_folderNames;
+ NSString *_fallbackFolderName;
+ _Bool _installing;
+ _Bool _uninstalling;
+ NSObject *_workQueue;
+}
+
+@property(nonatomic, getter=_isUninstalling, setter=_setUninstalling:) _Bool uninstalling; // @synthesize uninstalling=_uninstalling;
+@property(nonatomic, getter=_isInstalling, setter=_setInstalling:) _Bool installing; // @synthesize installing=_installing;
+@property(readonly, nonatomic) long long ratingRank; // @synthesize ratingRank=_ratingRank;
+@property(readonly, retain, nonatomic) NSArray *externalAccessoryProtocols; // @synthesize externalAccessoryProtocols=_externalAccessoryProtocols;
+@property(readonly, nonatomic) float minimumBrightnessLevel; // @synthesize minimumBrightnessLevel=_minimumBrightnessLevel;
+@property(readonly, nonatomic) _Bool requiresPersistentWiFi; // @synthesize requiresPersistentWiFi=_requiresPersistentWiFi;
+@property(readonly, nonatomic, getter=isExitsOnSuspend) _Bool exitsOnSuspend; // @synthesize exitsOnSuspend=_exitsOnSuspend;
+@property(readonly, nonatomic, getter=isBeta) _Bool beta; // @synthesize beta=_beta;
+@property(readonly, nonatomic, getter=isRestricted) _Bool restricted; // @synthesize restricted=_restricted;
+@property(readonly, nonatomic, getter=isNewsstand) _Bool newsstand; // @synthesize newsstand=_newsstand;
+@property(readonly, nonatomic, getter=isEnabled) _Bool enabled; // @synthesize enabled=_enabled;
+@property(readonly, retain, nonatomic) NSArray *tags; // @synthesize tags=_tags;
+@property(readonly, retain, nonatomic) NSArray *deviceFamilies; // @synthesize deviceFamilies=_deviceFamilies;
+@property(readonly, retain, nonatomic) NSArray *requiredCapabilities; // @synthesize requiredCapabilities=_requiredCapabilities;
+@property(readonly, nonatomic) unsigned long long type; // @synthesize type=_type;
+@property(readonly, retain, nonatomic) NSArray *customMachServices; // @synthesize customMachServices=_customMachServices;
+@property(readonly, copy, nonatomic) NSString *sdkVersion; // @synthesize sdkVersion=_sdkVersion;
+@property(readonly, nonatomic, getter=isProvisioningProfileValidated) _Bool provisioningProfileValidated; // @synthesize provisioningProfileValidated=_provisioningProfileValidated;
+@property(readonly, retain, nonatomic) NSDictionary *entitlements; // @synthesize entitlements=_entitlements;
+@property(readonly, retain, nonatomic) NSDictionary *environmentVariables; // @synthesize environmentVariables=_environmentVariables;
+@property(readonly, copy, nonatomic) NSString *signerIdentity; // @synthesize signerIdentity=_signerIdentity;
+@property(readonly, copy, nonatomic) NSString *preferenceDomain; // @synthesize preferenceDomain=_preferenceDomain;
+@property(readonly, nonatomic) double lastModifiedDate; // @synthesize lastModifiedDate=_lastModifiedDate;
+@property(readonly, retain, nonatomic) NSURL *sandboxURL; // @synthesize sandboxURL=_sandboxURL;
+@property(readonly, retain, nonatomic) NSURL *dataContainerURL; // @synthesize dataContainerURL=_dataContainerURL;
+@property(readonly, retain, nonatomic) NSURL *bundleContainerURL; // @synthesize bundleContainerURL=_bundleContainerURL;
+@property(readonly, retain, nonatomic) NSURL *executableURL; // @synthesize executableURL=_executableURL;
+- (id)_localizedGenreFromDictionary:(id)arg1;
+- (id)_localizedGenreNameForID:(long long)arg1;
+- (void)_cacheFolderNamesForSystemApp:(id)arg1;
+- (id)_configureEnvironment:(id)arg1;
+- (long long)_computeRatingRank;
+- (id)_copyiTunesMetadata;
+- (void)_buildDefaultsFromInfoPlist:(id)arg1;
+- (id)_computeSupportedInterfaceOrientations:(id)arg1;
+- (void)_acceptApplicationSignatureIdentity;
+- (id)_preferenceDomain;
+- (double)_lastModifiedDateForPath:(id)arg1;
+- (unsigned long long)_applicationType:(id)arg1;
+- (id)description;
+- (_Bool)builtOnOrAfterSDKVersion:(id)arg1;
+- (void)acceptApplicationSignatureIdentity;
+- (_Bool)supportsInterfaceOrientation:(long long)arg1;
+- (_Bool)supportsBackgroundMode:(id)arg1;
+@property(readonly, retain, nonatomic) NSString *fallbackFolderName; // @synthesize fallbackFolderName=_fallbackFolderName;
+@property(readonly, retain, nonatomic) NSArray *folderNames; // @synthesize folderNames=_folderNames;
+@property(readonly, nonatomic) long long signatureState; // @dynamic signatureState;
+- (void)dealloc;
+- (id)initWithApplicationProxy:(id)arg1;
+
+@end
diff --git a/needle-agent/needleAgent/Info.plist b/needle-agent/needleAgent/Info.plist
new file mode 100755
index 0000000..9257de8
--- /dev/null
+++ b/needle-agent/needleAgent/Info.plist
@@ -0,0 +1,57 @@
+
+
+
+
+ CF
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIcons
+
+ CFBundleIcons~ipad
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSApplicationCategoryType
+ public.app-category.developer-tools
+ LSRequiresIPhoneOS
+
+ UIBackgroundModes
+
+ voip
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/needle-agent/needleAgent/LSApplicationProxy.h b/needle-agent/needleAgent/LSApplicationProxy.h
new file mode 100644
index 0000000..de5ffc0
--- /dev/null
+++ b/needle-agent/needleAgent/LSApplicationProxy.h
@@ -0,0 +1,166 @@
+@interface LSApplicationProxy : NSObject {
+ NSArray *_UIBackgroundModes;
+ NSArray *_VPNPlugins;
+ NSArray *_appTags;
+ NSString *_applicationDSID;
+ NSArray *_audioComponents;
+ NSArray *_deviceFamily;
+ NSUUID *_deviceIdentifierForVendor;
+ NSArray *_directionsModes;
+ NSNumber *_dynamicDiskUsage;
+ NSArray *_externalAccessoryProtocols;
+ unsigned int _flags;
+ NSDictionary *_groupContainers;
+ NSArray *_groupIdentifiers;
+ unsigned int _installType;
+ BOOL _isContainerized;
+ NSNumber *_itemID;
+ NSString *_itemName;
+ NSString *_minimumSystemVersion;
+ long _modTime;
+ unsigned int _originalInstallType;
+ NSArray *_plugInKitPlugins;
+ NSArray *_pluginUUIDs;
+ NSArray *_privateDocumentIconNames;
+ LSApplicationProxy *_privateDocumentTypeOwner;
+ NSArray *_requiredDeviceCapabilities;
+ NSString *_sdkVersion;
+ NSString *_shortVersionString;
+ NSString *_sourceAppIdentifier;
+ NSNumber *_staticDiskUsage;
+ NSString *_storeCohortMetadata;
+ NSNumber *_storeFront;
+ NSString *_teamID;
+ NSString *_vendorName;
+}
+
+@property (nonatomic, readonly) NSArray *UIBackgroundModes;
+@property (nonatomic, readonly) NSArray *VPNPlugins;
+@property (nonatomic, readonly) NSArray *appTags;
+@property (nonatomic, readonly) NSString *applicationDSID;
+@property (nonatomic, readonly) NSString *applicationIdentifier;
+@property (nonatomic, readonly) NSString *applicationType;
+@property (nonatomic, readonly) NSArray *audioComponents;
+@property (nonatomic, readonly) NSString *bundleVersion;
+@property (nonatomic, readonly) NSArray *deviceFamily;
+@property (nonatomic, readonly) NSUUID *deviceIdentifierForVendor;
+@property (nonatomic, readonly) NSArray *directionsModes;
+@property (nonatomic, readonly) NSNumber *dynamicDiskUsage;
+@property (nonatomic, readonly) NSArray *externalAccessoryProtocols;
+@property (nonatomic, readonly) BOOL fileSharingEnabled;
+@property (nonatomic, readonly) NSDictionary *groupContainers;
+@property (nonatomic, readonly) NSArray *groupIdentifiers;
+@property (nonatomic, readonly) BOOL hasSettingsBundle;
+@property (nonatomic, readonly) BOOL iconIsPrerendered;
+@property (nonatomic, readonly) NSProgress *installProgress;
+@property (nonatomic, readonly) unsigned int installType;
+@property (nonatomic, readonly) BOOL isAppUpdate;
+@property (nonatomic, readonly) BOOL isBetaApp;
+@property (nonatomic, readonly) BOOL isContainerized;
+@property (nonatomic, readonly) BOOL isInstalled;
+@property (nonatomic, readonly) BOOL isNewsstandApp;
+@property (nonatomic, readonly) BOOL isPlaceholder;
+@property (nonatomic, readonly) BOOL isPurchasedReDownload;
+@property (nonatomic, readonly) BOOL isRestricted;
+@property (nonatomic, readonly) BOOL isWatchKitApp;
+@property (nonatomic, readonly) NSNumber *itemID;
+@property (nonatomic, readonly) NSString *itemName;
+@property (nonatomic, readonly) NSString *minimumSystemVersion;
+@property (nonatomic, readonly) unsigned int originalInstallType;
+@property (nonatomic, readonly) NSArray *plugInKitPlugins;
+@property (nonatomic, readonly) BOOL profileValidated;
+@property (nonatomic, readonly) NSArray *requiredDeviceCapabilities;
+@property (nonatomic, readonly) NSString *roleIdentifier;
+@property (nonatomic, readonly) NSString *sdkVersion;
+@property (nonatomic, readonly) NSString *shortVersionString;
+@property (nonatomic, readonly) NSString *sourceAppIdentifier;
+@property (nonatomic, readonly) NSNumber *staticDiskUsage;
+@property (nonatomic, readonly) NSString *storeCohortMetadata;
+@property (nonatomic, readonly) NSNumber *storeFront;
+@property (nonatomic, readonly) BOOL supportsAudiobooks;
+@property (nonatomic, readonly) BOOL supportsExternallyPlayableContent;
+@property (nonatomic, readonly) NSString *teamID;
+@property (nonatomic, readonly) NSString *vendorName;
+
+// Image: /System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices
+
++ (id)applicationProxyForBundleURL:(id)arg1;
++ (id)applicationProxyForIdentifier:(id)arg1;
++ (id)applicationProxyForIdentifier:(id)arg1 placeholder:(BOOL)arg2;
++ (id)applicationProxyForIdentifier:(id)arg1 roleIdentifier:(id)arg2;
++ (id)applicationProxyForItemID:(id)arg1;
++ (id)applicationProxyWithBundleUnitID:(unsigned long)arg1;
++ (BOOL)supportsSecureCoding;
+
+- (id)UIBackgroundModes;
+- (id)VPNPlugins;
+- (id)_initWithBundleUnit:(unsigned long)arg1 applicationIdentifier:(id)arg2;
+- (id)appStoreReceiptURL;
+- (id)appTags;
+- (id)applicationDSID;
+- (id)applicationIdentifier;
+- (id)applicationType;
+- (id)audioComponents;
+- (long)bundleModTime;
+- (void)dealloc;
+- (id)description;
+- (id)deviceFamily;
+- (id)deviceIdentifierForVendor;
+- (id)directionsModes;
+- (id)dynamicDiskUsage;
+- (void)encodeWithCoder:(id)arg1;
+- (id)externalAccessoryProtocols;
+- (BOOL)fileSharingEnabled;
+- (id)groupContainers;
+- (id)groupIdentifiers;
+- (BOOL)hasSettingsBundle;
+- (unsigned int)hash;
+- (id)iconDataForVariant:(int)arg1;
+- (BOOL)iconIsPrerendered;
+- (id)iconStyleDomain;
+- (id)initWithCoder:(id)arg1;
+- (id)installProgress;
+- (id)installProgressSync;
+- (unsigned int)installType;
+- (BOOL)isAppUpdate;
+- (BOOL)isBetaApp;
+- (BOOL)isContainerized;
+- (BOOL)isEqual:(id)arg1;
+- (BOOL)isInstalled;
+- (BOOL)isNewsstandApp;
+- (BOOL)isPlaceholder;
+- (BOOL)isPurchasedReDownload;
+- (BOOL)isRestricted;
+- (BOOL)isWatchKitApp;
+- (id)itemID;
+- (id)itemName;
+- (id)localizedName;
+- (id)localizedShortName;
+- (id)machOUUIDs;
+- (id)minimumSystemVersion;
+- (unsigned int)originalInstallType;
+- (id)plugInKitPlugins;
+- (void)populateNotificationData;
+- (BOOL)privateDocumentIconAllowOverride;
+- (id)privateDocumentIconNames;
+- (id)privateDocumentTypeOwner;
+- (BOOL)profileValidated;
+- (id)requiredDeviceCapabilities;
+- (id)resourcesDirectoryURL;
+- (id)roleIdentifier;
+- (id)sdkVersion;
+- (void)setPrivateDocumentIconAllowOverride:(BOOL)arg1;
+- (void)setPrivateDocumentIconNames:(id)arg1;
+- (void)setPrivateDocumentTypeOwner:(id)arg1;
+- (id)shortVersionString;
+- (id)sourceAppIdentifier;
+- (id)staticDiskUsage;
+- (id)storeCohortMetadata;
+- (id)storeFront;
+- (BOOL)supportsAudiobooks;
+- (BOOL)supportsExternallyPlayableContent;
+- (id)teamID;
+- (id)userActivityStringForAdvertisementData:(id)arg1;
+- (id)vendorName;
+
+@end
diff --git a/needle-agent/needleAgent/LSApplicationWorkspace.h b/needle-agent/needleAgent/LSApplicationWorkspace.h
new file mode 100644
index 0000000..a47da25
--- /dev/null
+++ b/needle-agent/needleAgent/LSApplicationWorkspace.h
@@ -0,0 +1,86 @@
+/*
+ * This header is generated by classdump-dyld 0.7
+ * on Sunday, November 22, 2015 at 10:24:36 PM Eastern Standard Time
+ * Operating System: Version 9.0.2 (Build 13A452)
+ * Image Source: /System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices
+ * classdump-dyld is licensed under GPLv3, Copyright © 2013 by Elias Limneos.
+ */
+
+
+@interface LSApplicationWorkspace : NSObject
++(id)defaultWorkspace;
+-(void)_sf_openURL:(id)arg1 withOptions:(id)arg2 completionHandler:(/*^block*/id)arg3 ;
+-(id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 userInfo:(id)arg3 ;
+-(BOOL)establishConnection;
+-(id)remoteObserver;
+-(id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3 applyFilter:(/*^block*/id)arg4 ;
+-(id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 uniqueDocumentIdentifier:(id)arg3 userInfo:(id)arg4 ;
+-(BOOL)installApplication:(id)arg1 withOptions:(id)arg2 error:(id*)arg3 usingBlock:(/*^block*/id)arg4 ;
+-(id)installProgressForApplication:(id)arg1 withPhase:(unsigned long long)arg2 ;
+-(BOOL)registerApplicationDictionary:(id)arg1 withObserverNotification:(int)arg2 ;
+-(BOOL)installPhaseFinishedForProgress:(id)arg1 ;
+-(unsigned long long)getInstallTypeForOptions:(id)arg1 andApp:(id)arg2 ;
+-(id)installBundle:(id)arg1 withOptions:(id)arg2 usingBlock:(/*^block*/id)arg3 forApp:(id)arg4 withError:(id*)arg5 outInstallProgress:(id*)arg6 ;
+-(BOOL)uninstallApplication:(id)arg1 withOptions:(id)arg2 usingBlock:(/*^block*/id)arg3 ;
+-(BOOL)registerBundleWithInfo:(id)arg1 options:(id)arg2 type:(unsigned long long)arg3 progress:(id)arg4 ;
+-(void)clearCreatedProgressForBundleID:(id)arg1 ;
+-(void)removeInstallProgressForBundleID:(id)arg1 ;
+-(void)getKnowledgeUUID:(id*)arg1 andSequenceNumber:(id*)arg2 ;
+-(id)delegateProxy;
+-(id)directionsApplications;
+-(id)applicationsWithAudioComponents;
+-(id)applicationsWithSettingsBundle;
+-(id)applicationsWithVPNPlugins;
+-(id)applicationsWithExternalAccessoryProtocols;
+-(id)applicationForUserActivityType:(id)arg1 ;
+-(id)applicationForUserActivityDomainName:(id)arg1 ;
+-(id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3 withFilter:(/*^block*/id)arg4 ;
+-(id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3 ;
+-(void)openUserActivity:(id)arg1 withApplicationProxy:(id)arg2 completionHandler:(/*^block*/id)arg3 ;
+-(id)installedVPNPlugins;
+-(id)unrestrictedApplications;
+-(id)publicURLSchemes;
+-(BOOL)getClaimedActivityTypes:(id*)arg1 domains:(id*)arg2 ;
+-(BOOL)installApplication:(id)arg1 withOptions:(id)arg2 ;
+-(BOOL)installApplication:(id)arg1 withOptions:(id)arg2 error:(id*)arg3 ;
+-(BOOL)downgradeApplicationToPlaceholder:(id)arg1 withOptions:(id)arg2 error:(id*)arg3 ;
+-(BOOL)registerApplicationDictionary:(id)arg1 ;
+-(BOOL)registerApplication:(id)arg1 ;
+-(BOOL)unregisterApplication:(id)arg1 ;
+-(BOOL)registerPlugin:(id)arg1 ;
+-(BOOL)unregisterPlugin:(id)arg1 ;
+-(BOOL)updateSINFWithData:(id)arg1 forApplication:(id)arg2 options:(id)arg3 error:(id*)arg4 ;
+-(BOOL)invalidateIconCache:(id)arg1 ;
+-(void)_clearCachedAdvertisingIdentifier;
+-(id)deviceIdentifierForAdvertising;
+-(id)installProgressForBundleID:(id)arg1 makeSynchronous:(unsigned char)arg2 ;
+-(BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3 ;
+-(id)applicationForOpeningResource:(id)arg1 ;
+-(void)addObserver:(id)arg1 ;
+-(BOOL)isApplicationAvailableToOpenURL:(id)arg1 error:(id*)arg2 ;
+-(id)applicationsAvailableForHandlingURLScheme:(id)arg1 ;
+-(id)privateURLSchemes;
+-(id)URLOverrideForURL:(id)arg1 ;
+-(BOOL)openURL:(id)arg1 ;
+-(void)removeObserver:(id)arg1 ;
+-(id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 uniqueDocumentIdentifier:(id)arg3 userInfo:(id)arg4 delegate:(id)arg5 ;
+-(id)deviceIdentifierForVendor;
+-(id)applicationsAvailableForOpeningDocument:(id)arg1 ;
+-(id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 uniqueDocumentIdentifier:(id)arg3 sourceIsManaged:(BOOL)arg4 userInfo:(id)arg5 delegate:(id)arg6 ;
+-(BOOL)openURL:(id)arg1 withOptions:(id)arg2 ;
+-(BOOL)openSensitiveURL:(id)arg1 withOptions:(id)arg2 ;
+-(id)allInstalledApplications;
+-(id)applicationsOfType:(unsigned long long)arg1 ;
+-(void)enumerateBundlesOfType:(unsigned long long)arg1 usingBlock:(/*^block*/id)arg2 ;
+-(BOOL)uninstallApplication:(id)arg1 withOptions:(id)arg2 ;
+-(id)placeholderApplications;
+-(BOOL)applicationIsInstalled:(id)arg1 ;
+-(id)installedPlugins;
+-(void)_LSClearSchemaCaches;
+-(void)clearAdvertisingIdentifier;
+-(id)applicationsWithUIBackgroundModes;
+-(id)allApplications;
+-(BOOL)openApplicationWithBundleID:(id)arg1 ;
+-(BOOL)openSensitiveURL:(id)arg1 withOptions:(id)arg2 error:(id*)arg3 ;
+-(BOOL)openURL:(id)arg1 withOptions:(id)arg2 error:(id*)arg3 ;
+@end
diff --git a/needle-agent/needleAgent/MWRAsyncSocket.h b/needle-agent/needleAgent/MWRAsyncSocket.h
new file mode 100755
index 0000000..7df0e20
--- /dev/null
+++ b/needle-agent/needleAgent/MWRAsyncSocket.h
@@ -0,0 +1,18 @@
+//
+// MWRAsyncSocket.h
+// needleAgent
+//
+
+#import "MWRCommandHandler.h"
+@import CocoaAsyncSocket;
+
+
+@interface MWRAsyncSocket : GCDAsyncSocket
+
+@property MWRCommandHandler *handler;
+
+-(instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
+-(void)interruptLog;
+-(void) writeString:(NSString *)string withCRLF:(BOOL)crlf;
+
+@end
diff --git a/needle-agent/needleAgent/MWRAsyncSocket.m b/needle-agent/needleAgent/MWRAsyncSocket.m
new file mode 100755
index 0000000..e840950
--- /dev/null
+++ b/needle-agent/needleAgent/MWRAsyncSocket.m
@@ -0,0 +1,36 @@
+//
+// MWRAsyncSocket.m
+// needleAgent
+//
+
+#import "MWRAsyncSocket.h"
+
+@implementation MWRAsyncSocket
+
+-(instancetype)initWithDelegate:(id)aDelegate
+ delegateQueue:(dispatch_queue_t)dq
+ socketQueue:(dispatch_queue_t)sq
+{
+ self = [super initWithDelegate:aDelegate delegateQueue: dq socketQueue:sq];
+ self.handler = [[MWRCommandHandler alloc] initWithSocket:self];
+ return self;
+}
+
+-(void) interruptLog
+{
+ self.handler.interrupt = YES;
+}
+
+-(void) writeString:(NSString *)string withCRLF:(BOOL)crlf
+{
+ NSString *response;
+ if(crlf)
+ response = [NSString stringWithFormat:@"%@\r\n",string];
+ else
+ response = string;
+
+ NSData *writeData = [response dataUsingEncoding:NSUTF8StringEncoding];
+ [self writeData:writeData withTimeout:-1 tag:0];
+}
+
+@end
diff --git a/needle-agent/needleAgent/MWRCommandHandler.h b/needle-agent/needleAgent/MWRCommandHandler.h
new file mode 100755
index 0000000..6bf58da
--- /dev/null
+++ b/needle-agent/needleAgent/MWRCommandHandler.h
@@ -0,0 +1,24 @@
+//
+// MWRCommandHandler.h
+// needleAgent
+//
+
+#import
+#import
+#import
+#import
+@import CocoaAsyncSocket;
+
+@class MWRAsyncSocket;
+
+@interface MWRCommandHandler : NSObject
+
+@property BOOL interrupt;
+@property (weak) MWRAsyncSocket* socket;
+
+-(instancetype)initWithSocket:(MWRAsyncSocket *)socket;
+-(NSString *)handleCommand:(NSString *)request;
+-(NSString *)handleOpCode:(NSString *)opCode withArguments:(NSArray *)arguments;
+-(NSArray *)parseRequest:(NSString *)arguments;
+
+@end
diff --git a/needle-agent/needleAgent/MWRCommandHandler.m b/needle-agent/needleAgent/MWRCommandHandler.m
new file mode 100755
index 0000000..4425618
--- /dev/null
+++ b/needle-agent/needleAgent/MWRCommandHandler.m
@@ -0,0 +1,81 @@
+//
+// MWRCommandHandler.m
+// needleAgent
+//
+
+#import "MWRCommandHandler.h"
+#import "MWRAsyncSocket.h"
+#import "Constants.h"
+
+
+@implementation MWRCommandHandler
+
+-(instancetype)initWithSocket:(MWRAsyncSocket *)socket
+{
+ self = [super init];
+ self.socket = socket;
+ self.interrupt = NO;
+ return self;
+}
+
+-(NSString *)handleCommand:(NSString *)request
+{
+ if(request){
+ NSArray *formattedRequest = [self parseRequest:request];
+ NSString *opCode = [formattedRequest[0] uppercaseString];
+ NSArray *arguments = [formattedRequest subarrayWithRange:NSMakeRange(1, [formattedRequest count]-1)];
+ NSLog(@"Handle Command: %@, %@", opCode, arguments);
+ return [self handleOpCode:opCode withArguments:arguments];
+ } else {
+ return @"Invalid command";
+ }
+}
+
+-(NSString *)handleOpCode:(NSString *)opCode withArguments:(NSArray *)arguments
+{
+ SEL method = NSSelectorFromString(@"run:");
+ NSString *classname = [NSString stringWithFormat:@"Opcode%@", opCode];
+ Class class = NSClassFromString(classname);
+
+ if([class respondsToSelector:method]){
+ IMP imp = [class methodForSelector:method];
+ NSString * (*func)(id, SEL, NSArray *) = (void *)imp;
+ return func(class, method, arguments);
+ }
+
+ return @"Invalid Opcode";
+}
+
+-(NSArray *)parseRequest:(NSString *)request
+{
+ NSError *err = nil;
+
+ // Matches 1 or more whitespace characters
+ NSString *excessWhiteSpace = @"\\s+";
+ NSRegularExpression *excessRegex = [NSRegularExpression
+ regularExpressionWithPattern:excessWhiteSpace
+ options:0
+ error:&err];
+ // Matches space character at end of string
+ NSString *trailingWhiteSpace = @" $";
+ NSRegularExpression *trailingRegex = [NSRegularExpression
+ regularExpressionWithPattern:trailingWhiteSpace
+ options:0
+ error:&err];
+ // Replaces any groups of whitespace characters with a single space character
+ NSString *normailzed = [excessRegex
+ stringByReplacingMatchesInString:request
+ options:0
+ range:NSMakeRange(0, [request length])
+ withTemplate:@" "];
+ // Removes space character at end of string if there is one
+ NSString *final = [trailingRegex
+ stringByReplacingMatchesInString:normailzed
+ options:0
+ range:NSMakeRange(0, [normailzed length])
+ withTemplate:@""];
+
+ return [final componentsSeparatedByString:@" "];
+}
+
+@end
diff --git a/needle-agent/needleAgent/MWRServerViewController.h b/needle-agent/needleAgent/MWRServerViewController.h
new file mode 100755
index 0000000..72044a5
--- /dev/null
+++ b/needle-agent/needleAgent/MWRServerViewController.h
@@ -0,0 +1,27 @@
+//
+// MWRServerViewController.h
+// needleAgent
+//
+
+#import
+#import "MWRCommandHandler.h"
+#import "MWRAsyncSocket.h"
+@import CocoaAsyncSocket;
+
+
+@interface MWRServerViewController : UIViewController {}
+
+@property (strong, nonatomic) IBOutlet UILabel *versionLabel;
+@property (weak, nonatomic) IBOutlet UILabel *responsesLabel;
+@property (weak, nonatomic) IBOutlet UITextField *portTextField;
+@property NSMutableString *responsesString;
+@property NSMutableArray *connectedSockets;
+@property dispatch_queue_t socketQueue;
+@property (nonatomic) MWRAsyncSocket *listenerSocket;
+
+-(void)dismissKeyboard;
+-(IBAction)startStop:(id)sender;
+-(void)listen:(UISwitch *)s;
+-(void)disconnect;
+
+@end
diff --git a/needle-agent/needleAgent/MWRServerViewController.m b/needle-agent/needleAgent/MWRServerViewController.m
new file mode 100755
index 0000000..be9aaed
--- /dev/null
+++ b/needle-agent/needleAgent/MWRServerViewController.m
@@ -0,0 +1,182 @@
+//
+// MWRServerViewController.m
+// needleAgent
+//
+
+#import "MWRServerViewController.h"
+#import "Constants.h"
+
+@implementation MWRServerViewController
+
+// ---------------------------------------------------------------------------
+// UI
+// ---------------------------------------------------------------------------
+// Use this initaliser because View Controller is called from Storyboard
+-(instancetype)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+
+ // Sockets will run on seperate thread/queue to UI
+ self.socketQueue = dispatch_queue_create("socketQueue", NULL);
+ self.listenerSocket = [[MWRAsyncSocket alloc] initWithDelegate:self delegateQueue:self.socketQueue];
+
+ // Keeps track of new client sockets and stops them getting instantly deallocated
+ self.connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
+
+ self.responsesString = [NSMutableString string];
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // Update standard port
+ self.portTextField.text = [NSString stringWithFormat:@"%@", DEFAULT_PORT];
+
+ // Update versionLabel
+ self.versionLabel.text = [NSString stringWithFormat:@"V.%@", AGENT_VERSION];
+
+ // Allow number keyboard to be dismissed by tapping outside the keyboard
+ UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(dismissKeyboard)];
+ [self.view addGestureRecognizer:tap];
+}
+
+-(void)dismissKeyboard
+{
+ [self.portTextField resignFirstResponder];
+}
+
+-(void)updateLogs:(NSString *)message
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // Prepend to responses string so messages appear at top of screen
+ [self.responsesString
+ appendString:[NSString stringWithFormat:@"> %@\n", message]];
+ // Update UILabel with new responses
+ self.responsesLabel.text = self.responsesString;
+ });
+}
+
+
+// ---------------------------------------------------------------------------
+// AGENT SERVER
+// ---------------------------------------------------------------------------
+-(void)listen:(UISwitch *)s
+{
+ // Setup port
+ int port = [self.portTextField.text intValue];
+ if(port < 1025 || port > 65535) self.portTextField.text = DEFAULT_PORT;
+ // Check for errors
+ NSError *error = nil;
+ if (![self.listenerSocket acceptOnPort:port error:&error])
+ {
+ [self updateLogs:[NSString stringWithFormat:@"ERROR: %@", error]];
+ [s setOn:NO];
+ } else {
+ [self updateLogs:@"Listening"];
+ [self.portTextField setEnabled:NO];
+ }
+}
+
+-(void)disconnect
+{
+ // Update UI
+ [self.portTextField setEnabled:YES];
+ // Stop any client connections
+ [self.listenerSocket disconnect];
+ @synchronized(self.connectedSockets)
+ {
+ NSUInteger i;
+ for (i = 0; i < [self.connectedSockets count]; i++)
+ {
+ // Call disconnect on the socket,
+ // which will invoke the socketDidDisconnect: method,
+ // which will remove the socket from the list.
+ [[self.connectedSockets objectAtIndex:i] disconnect];
+ }
+ }
+ [self updateLogs:@"Stopped Listening"];
+}
+
+- (IBAction)startStop:(id)sender
+{
+ // Starts and stops socket listening
+ UISwitch *s = (UISwitch *)sender;
+ if
+ ([s isOn]) [self listen:s];
+ else
+ [self disconnect];
+}
+
+- (void)socket:(GCDAsyncSocket *)sender didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+{
+ @synchronized(self.connectedSockets)
+ {
+ if([self.connectedSockets count] > 0)
+ {
+ // We already have a client connected, reject the request
+ [self updateLogs:[NSString stringWithFormat:@"A client is already connected, rejecting new connection request from: %@",
+ newSocket.connectedHost]];
+ return;
+ }
+ else
+ {
+ // First connection, accept it
+ [self.connectedSockets addObject:newSocket];
+ }
+ }
+ [newSocket performBlock:^{
+ [newSocket enableBackgroundingOnSocket];
+ }];
+ [sender performBlock:^{
+ [sender enableBackgroundingOnSocket];
+ }];
+
+ // Send welcome string to client
+ [self updateLogs:[NSString stringWithFormat:@"New connection from: %@", newSocket.connectedHost]];
+ NSString *welcomeMsg = AGENT_WELCOME;
+ NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
+ [newSocket writeData:welcomeData withTimeout:-1 tag:1];
+ [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1];
+ // Send version to client
+ NSData *versionData = [[NSString stringWithFormat:@"\n%@: %@\n", COMMAND_VERSION, AGENT_VERSION] dataUsingEncoding:NSUTF8StringEncoding];
+ [newSocket writeData:versionData withTimeout:-1 tag:1];
+ [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1];
+}
+
+-(void)socket:(GCDAsyncSocket *)sock didReadData:(nonnull NSData *)data withTag:(long)tag
+{
+ // Strip extra newline
+ NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
+ NSString *request = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
+ // Update Logs
+ if ([request length] > 0)
+ {
+ [self updateLogs:[NSString stringWithFormat:@"[%@] OPCODE: %@", sock.connectedHost, request]];
+ // Handle OPCODE
+ NSString *msg = [((MWRAsyncSocket *)sock).handler handleCommand:request];
+ if([msg isEqualToString:COMMAND_DISCONNECT]){
+ [sock disconnect];
+ return;
+ }
+ [(MWRAsyncSocket *)sock writeString:msg withCRLF:YES];
+ [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1];
+ }
+}
+
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
+{
+ if (sock != self.listenerSocket)
+ {
+ [self updateLogs:@"Client Disconnected"];
+ @synchronized(self.connectedSockets)
+ {
+ [self.connectedSockets removeObject:sock];
+ }
+ }
+}
+
+@end
diff --git a/needle-agent/needleAgent/Main.storyboard b/needle-agent/needleAgent/Main.storyboard
new file mode 100755
index 0000000..c83be7d
--- /dev/null
+++ b/needle-agent/needleAgent/Main.storyboard
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/needle-agent/needleAgent/OpcodeLIST_APPS.m b/needle-agent/needleAgent/OpcodeLIST_APPS.m
new file mode 100644
index 0000000..9b4a45d
--- /dev/null
+++ b/needle-agent/needleAgent/OpcodeLIST_APPS.m
@@ -0,0 +1,68 @@
+//
+// OpcodeLIST_APPS.m
+// needleAgent
+//
+
+#import "OpcodeProtocol.h"
+#import "LSApplicationWorkspace.h"
+#import "FBApplicationInfo.h"
+#import "LSApplicationProxy.h"
+
+@interface OpcodeLIST_APPS : NSObject
+@end
+
+
+@implementation OpcodeLIST_APPS
+
++(NSString *)run:(NSArray *)args
+{
+ NSString *res = [self listApplications];
+ NSString * responseString = [NSString stringWithFormat:@"%@: %@", COMMAND_RETURN, res];
+ return responseString;
+}
+
++ (NSString *)listApplications
+{
+ NSMutableDictionary *all_apps = [NSMutableDictionary new];
+ NSDictionary *bundleInfo = nil;
+
+ LSApplicationWorkspace *applicationWorkspace = [LSApplicationWorkspace defaultWorkspace];
+ NSArray *proxies = [applicationWorkspace allApplications];
+
+ for (FBApplicationInfo *proxy in proxies)
+ {
+ NSString *appType = [proxy performSelector:@selector(applicationType)];
+
+ if ([appType isEqualToString:@"User"] && proxy.bundleContainerURL && proxy.bundleURL)
+ {
+ NSString *itemName = ((LSApplicationProxy*)proxy).itemName;
+ if (!itemName)
+ {
+ itemName = ((LSApplicationProxy*)proxy).localizedName;
+ }
+
+ bundleInfo = @{
+ @"BundleURL": [proxy.bundleURL absoluteString],
+ @"BundleContainer": [proxy.bundleContainerURL absoluteString],
+ @"DataContainer": [proxy.dataContainerURL absoluteString],
+ @"DisplayName": itemName,
+ @"BundleIdentifier": proxy.bundleIdentifier,
+ @"BundleVersion": proxy.bundleVersion,
+ @"BundleURL": [proxy.bundleURL absoluteString],
+ @"Entitlements": proxy.entitlements,
+ @"SDKVersion": proxy.sdkVersion,
+ @"MinimumOS": ((LSApplicationProxy*)proxy).minimumSystemVersion,
+ @"TeamID": ((LSApplicationProxy*)proxy).teamID,
+ };
+ all_apps[proxy.bundleIdentifier] = bundleInfo;
+ }
+ }
+
+ // Convert it to JSON
+ NSError * err;
+ NSData * jsonData = [NSJSONSerialization dataWithJSONObject:all_apps options:0 error:&err];
+ return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+}
+
+@end
+
diff --git a/needle-agent/needleAgent/OpcodeOS_VERSION.m b/needle-agent/needleAgent/OpcodeOS_VERSION.m
new file mode 100644
index 0000000..3e0a3d6
--- /dev/null
+++ b/needle-agent/needleAgent/OpcodeOS_VERSION.m
@@ -0,0 +1,27 @@
+//
+// OpcodeOS_VERSION.m
+// needleAgent
+//
+#import "OpcodeProtocol.h"
+
+@interface OpcodeOS_VERSION : NSObject
+@end
+
+
+@implementation OpcodeOS_VERSION
+
++(NSString *)run:(NSArray *)args
+{
+ NSString *res = [self getOSVersion];
+ NSString * responseString = [NSString stringWithFormat:@"%@: %@", COMMAND_RETURN, res];
+ return responseString;
+}
+
++ (NSString *)getOSVersion
+{
+ NSOperatingSystemVersion ver = [[NSProcessInfo processInfo] operatingSystemVersion];
+ return [NSString stringWithFormat:@"%ld", ver.majorVersion];
+}
+
+@end
+
diff --git a/needle-agent/needleAgent/OpcodeProtocol.h b/needle-agent/needleAgent/OpcodeProtocol.h
new file mode 100644
index 0000000..bb9f7cb
--- /dev/null
+++ b/needle-agent/needleAgent/OpcodeProtocol.h
@@ -0,0 +1,13 @@
+//
+// OpcodeProtocol.h
+// needleAgent
+//
+
+#import
+#import "Constants.h"
+
+@protocol OPCODE
+
++(NSString *)run:(NSArray *)args;
+
+@end
diff --git a/needle-agent/needleAgent/OpcodeSTOP.m b/needle-agent/needleAgent/OpcodeSTOP.m
new file mode 100644
index 0000000..f11a5a6
--- /dev/null
+++ b/needle-agent/needleAgent/OpcodeSTOP.m
@@ -0,0 +1,20 @@
+//
+// OpcodeSTOP.m
+// needleAgent
+//
+
+#import "OpcodeProtocol.h"
+#import "MWRCommandHandler.h"
+
+@interface OpcodeSTOP : NSObject
+@end
+
+
+@implementation OpcodeSTOP
+
++(NSString *)run:(NSArray *)args
+{
+ [[MWRCommandHandler alloc] init].interrupt = YES;
+ return COMMAND_DISCONNECT;
+}
+@end
diff --git a/needle-agent/needleAgent/main.m b/needle-agent/needleAgent/main.m
new file mode 100755
index 0000000..b716295
--- /dev/null
+++ b/needle-agent/needleAgent/main.m
@@ -0,0 +1,13 @@
+//
+// Constants.h
+// needleAgent
+//
+
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/needle-agent/needleAgent/needleAgent.entitlements b/needle-agent/needleAgent/needleAgent.entitlements
new file mode 100755
index 0000000..52ae012
--- /dev/null
+++ b/needle-agent/needleAgent/needleAgent.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ keychain-access-groups
+
+ *
+
+
+
diff --git a/release/CydiaIcon.png b/release/CydiaIcon.png
new file mode 100644
index 0000000..5bdc7d6
Binary files /dev/null and b/release/CydiaIcon.png differ
diff --git a/release/NeedleAgent.deb b/release/NeedleAgent.deb
new file mode 100644
index 0000000..d19057f
Binary files /dev/null and b/release/NeedleAgent.deb differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@2x.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@2x.png
new file mode 100644
index 0000000..4c19d63
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@2x.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@3x.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@3x.png
new file mode 100644
index 0000000..1f69f26
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29@3x.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29~ipad.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29~ipad.png
new file mode 100644
index 0000000..2a67e87
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon29x29~ipad.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@2x.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@2x.png
new file mode 100644
index 0000000..ae8ece2
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@2x.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@3x.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@3x.png
new file mode 100644
index 0000000..c94eb25
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon60x60@3x.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76@2x~ipad.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76@2x~ipad.png
new file mode 100644
index 0000000..348a2a0
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76@2x~ipad.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76~ipad.png b/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76~ipad.png
new file mode 100644
index 0000000..948063c
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/AppIcon76x76~ipad.png differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Assets.car b/release/NeedleAgent/Applications/needleAgent.app/Assets.car
new file mode 100644
index 0000000..018e540
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Assets.car differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib
new file mode 100644
index 0000000..7886946
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/Info.plist b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/Info.plist
new file mode 100644
index 0000000..32288e8
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/Info.plist differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib
new file mode 100644
index 0000000..a43def6
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket
new file mode 100755
index 0000000..ed8ea5c
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/Info.plist b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/Info.plist
new file mode 100644
index 0000000..7e587ae
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/Info.plist differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/_CodeSignature/CodeResources b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/_CodeSignature/CodeResources
new file mode 100644
index 0000000..42f0f64
--- /dev/null
+++ b/release/NeedleAgent/Applications/needleAgent.app/Frameworks/CocoaAsyncSocket.framework/_CodeSignature/CodeResources
@@ -0,0 +1,120 @@
+
+
+
+
+ files
+
+ Info.plist
+
+ 9Tsjb7n3tpU4ISFtc60sJoBiin0=
+
+
+ files2
+
+ rules
+
+ ^
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^
+
+ weight
+ 20
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Info.plist b/release/NeedleAgent/Applications/needleAgent.app/Info.plist
new file mode 100644
index 0000000..0d76a63
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Info.plist differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/BF7-WO-YOG-view-ue0-Jf-hdt.nib b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/BF7-WO-YOG-view-ue0-Jf-hdt.nib
new file mode 100644
index 0000000..c83e213
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/BF7-WO-YOG-view-ue0-Jf-hdt.nib differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/Info.plist b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/Info.plist
new file mode 100644
index 0000000..4be3edf
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/Info.plist differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/UINavigationController-EVn-Gp-ziU.nib b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/UINavigationController-EVn-Gp-ziU.nib
new file mode 100644
index 0000000..ada6a55
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/Main.storyboardc/UINavigationController-EVn-Gp-ziU.nib differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/PkgInfo b/release/NeedleAgent/Applications/needleAgent.app/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/release/NeedleAgent/Applications/needleAgent.app/PkgInfo
@@ -0,0 +1 @@
+APPL????
\ No newline at end of file
diff --git a/release/NeedleAgent/Applications/needleAgent.app/_CodeSignature/CodeResources b/release/NeedleAgent/Applications/needleAgent.app/_CodeSignature/CodeResources
new file mode 100644
index 0000000..61c6ca4
--- /dev/null
+++ b/release/NeedleAgent/Applications/needleAgent.app/_CodeSignature/CodeResources
@@ -0,0 +1,395 @@
+
+
+
+
+ files
+
+ AppIcon29x29@2x.png
+
+ NvQcy6Ic3XmaO2hZlbRbINEtyio=
+
+ AppIcon29x29@3x.png
+
+ L5oeparCEMXMFVHaVGa7/WlAARg=
+
+ AppIcon29x29~ipad.png
+
+ EZXe2gLFiAg61Y4fSHRXk+rQbYM=
+
+ AppIcon60x60@2x.png
+
+ X6RukrZRUTyE3KoPFIcfxPp5FC4=
+
+ AppIcon60x60@3x.png
+
+ BQ2VrmW1R2r8acGUSG4D1X3tU1Q=
+
+ AppIcon76x76@2x~ipad.png
+
+ 4Lcu7NTuqYudv5pllpOGzHEgpo0=
+
+ AppIcon76x76~ipad.png
+
+ yr4HbFAY2rfIHPO7LMrpYkC0Qp8=
+
+ Assets.car
+
+ 2jVRn11ae1rHpsokxZejiHAiHaw=
+
+ Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib
+
+ fSn+zsDyMqQk4y86ddJ24h612Ko=
+
+ Base.lproj/LaunchScreen.storyboardc/Info.plist
+
+ n2t8gsDpfE6XkhG31p7IQJRxTxU=
+
+ Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib
+
+ EUpqpfsD1OCIu+86C0tSSDJrDPo=
+
+ Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket
+
+ Kg9lQNVMi18uwHUzAmB3t90gTpQ=
+
+ Frameworks/CocoaAsyncSocket.framework/Info.plist
+
+ 9Tsjb7n3tpU4ISFtc60sJoBiin0=
+
+ Frameworks/CocoaAsyncSocket.framework/_CodeSignature/CodeResources
+
+ A00tY221J2L322YnIC5faKYPJ1k=
+
+ Info.plist
+
+ cAAeUQ1bBl4VuyDqU4nfu+UY8/A=
+
+ Main.storyboardc/BF7-WO-YOG-view-ue0-Jf-hdt.nib
+
+ jZFwaDdbnvcmGA0Cq7AFOAQP4pY=
+
+ Main.storyboardc/Info.plist
+
+ ZS37Y9v7m+LMZu/RwxHyx/OvsiY=
+
+ Main.storyboardc/UINavigationController-EVn-Gp-ziU.nib
+
+ bH1GbtxDLv3vIuA0AeYFieXM2o0=
+
+ PkgInfo
+
+ n57qDP4tZfLD1rCS43W0B4LQjzE=
+
+ embedded.mobileprovision
+
+ ZKw5w8jPISHux3BuqdpqQohCkco=
+
+
+ files2
+
+ AppIcon29x29@2x.png
+
+ hash
+
+ NvQcy6Ic3XmaO2hZlbRbINEtyio=
+
+ hash2
+
+ Xkbv9nZcCqpsSXH+rxlsq4hbtiz6d5c0MmfNywonLoo=
+
+
+ AppIcon29x29@3x.png
+
+ hash
+
+ L5oeparCEMXMFVHaVGa7/WlAARg=
+
+ hash2
+
+ nNkz5zavCRlSXEywrWCXj78JJI2rpw8jSq+tgGfVVis=
+
+
+ AppIcon29x29~ipad.png
+
+ hash
+
+ EZXe2gLFiAg61Y4fSHRXk+rQbYM=
+
+ hash2
+
+ RBlcYRD/kK3cuRsbRxs0Wi77sSoELEj6shQYj+6rWwU=
+
+
+ AppIcon60x60@2x.png
+
+ hash
+
+ X6RukrZRUTyE3KoPFIcfxPp5FC4=
+
+ hash2
+
+ Wu7CAQ2FV3jW+XlFULfOJyJSVIB9UbsJDRsiOVC3rDs=
+
+
+ AppIcon60x60@3x.png
+
+ hash
+
+ BQ2VrmW1R2r8acGUSG4D1X3tU1Q=
+
+ hash2
+
+ 2R2HiOwW6vIi8IbIYczkXSp/f9vDMkBC2vBeUzyQkck=
+
+
+ AppIcon76x76@2x~ipad.png
+
+ hash
+
+ 4Lcu7NTuqYudv5pllpOGzHEgpo0=
+
+ hash2
+
+ CB+wg/24ygOFTEiZt3qWTW4c418JZxgKj1K7akZMWAk=
+
+
+ AppIcon76x76~ipad.png
+
+ hash
+
+ yr4HbFAY2rfIHPO7LMrpYkC0Qp8=
+
+ hash2
+
+ lRuKGo3BvTZyybu013gXDfUHU8wDxtV+T/TkfPsXoTw=
+
+
+ Assets.car
+
+ hash
+
+ 2jVRn11ae1rHpsokxZejiHAiHaw=
+
+ hash2
+
+ ab/NTbLtNYxRdHiawT/o9r2Yz7yVdWQoVOviKUae+LU=
+
+
+ Base.lproj/LaunchScreen.storyboardc/01J-lp-oVM-view-Ze5-6b-2t3.nib
+
+ hash
+
+ fSn+zsDyMqQk4y86ddJ24h612Ko=
+
+ hash2
+
+ UwBbvUriZM0aRcmhVgKLxF2iHVCx2fBL3tgZCOBkOnM=
+
+
+ Base.lproj/LaunchScreen.storyboardc/Info.plist
+
+ hash
+
+ n2t8gsDpfE6XkhG31p7IQJRxTxU=
+
+ hash2
+
+ HyVdXMU7Ux4/KalAao30mpWOK/lEPT4gvYN09wf31cg=
+
+
+ Base.lproj/LaunchScreen.storyboardc/UIViewController-01J-lp-oVM.nib
+
+ hash
+
+ EUpqpfsD1OCIu+86C0tSSDJrDPo=
+
+ hash2
+
+ 0Zn00YiJpXWizfzX6ziTqP6TBCJZ7slfXMp7d6KNQ4w=
+
+
+ Frameworks/CocoaAsyncSocket.framework/CocoaAsyncSocket
+
+ hash
+
+ Kg9lQNVMi18uwHUzAmB3t90gTpQ=
+
+ hash2
+
+ nC2P9BmCFwXiaeJfZ7n0a+W5Dk011QCZLJ+0BhE7hVY=
+
+
+ Frameworks/CocoaAsyncSocket.framework/Info.plist
+
+ hash
+
+ 9Tsjb7n3tpU4ISFtc60sJoBiin0=
+
+ hash2
+
+ Yyt6K1TcZ5FRlFdoxpEkYuDK8aqNasoCGgAC5OV06aY=
+
+
+ Frameworks/CocoaAsyncSocket.framework/_CodeSignature/CodeResources
+
+ hash
+
+ A00tY221J2L322YnIC5faKYPJ1k=
+
+ hash2
+
+ lybuzR3DhLCy9dgCO4zvj9wjXpfrxE6i+UsP1AGlRms=
+
+
+ Main.storyboardc/BF7-WO-YOG-view-ue0-Jf-hdt.nib
+
+ hash
+
+ jZFwaDdbnvcmGA0Cq7AFOAQP4pY=
+
+ hash2
+
+ DruhMIdROQ/hwMY0GeeJSeICUTFnBAgDLwiXVE/2EwQ=
+
+
+ Main.storyboardc/Info.plist
+
+ hash
+
+ ZS37Y9v7m+LMZu/RwxHyx/OvsiY=
+
+ hash2
+
+ 8kKn3HwxcD9KSYhMBVH7FepauYRE4jfcWoT0J3bGt8g=
+
+
+ Main.storyboardc/UINavigationController-EVn-Gp-ziU.nib
+
+ hash
+
+ bH1GbtxDLv3vIuA0AeYFieXM2o0=
+
+ hash2
+
+ UbZ/juZhma4Qu4bFMcP/GyPmBfkp5hnaPEKIo68qfcA=
+
+
+ embedded.mobileprovision
+
+ hash
+
+ ZKw5w8jPISHux3BuqdpqQohCkco=
+
+ hash2
+
+ U9GXAV/Rdhv1fVc0pySYZk2mHcUDjmgOAfbdRRur87A=
+
+
+
+ rules
+
+ ^
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^
+
+ weight
+ 20
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Base\.lproj/
+
+ weight
+ 1010
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/release/NeedleAgent/Applications/needleAgent.app/embedded.mobileprovision b/release/NeedleAgent/Applications/needleAgent.app/embedded.mobileprovision
new file mode 100644
index 0000000..d6809bd
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/embedded.mobileprovision differ
diff --git a/release/NeedleAgent/Applications/needleAgent.app/needleAgent b/release/NeedleAgent/Applications/needleAgent.app/needleAgent
new file mode 100755
index 0000000..e5a25c8
Binary files /dev/null and b/release/NeedleAgent/Applications/needleAgent.app/needleAgent differ
diff --git a/release/NeedleAgent/DEBIAN/control b/release/NeedleAgent/DEBIAN/control
new file mode 100644
index 0000000..9af25ef
--- /dev/null
+++ b/release/NeedleAgent/DEBIAN/control
@@ -0,0 +1,10 @@
+Package: mwr.needle.agent
+Name: NeedleAgent
+Version: 1.0
+Architecture: iphoneos-arm
+Description: Needle Agent
+Homepage: http://mobiletools.mwrinfosecurity.com
+Maintainer: Marco Lancini
+Author: Marco Lancini
+Sponsor: MWR InfoSecurity
+Section: Develpment
diff --git a/release/Packages.bz2 b/release/Packages.bz2
new file mode 100644
index 0000000..e8bc62d
Binary files /dev/null and b/release/Packages.bz2 differ
diff --git a/release/Packages.gz b/release/Packages.gz
new file mode 100644
index 0000000..a278917
Binary files /dev/null and b/release/Packages.gz differ
diff --git a/release/Release b/release/Release
new file mode 100644
index 0000000..7790c80
--- /dev/null
+++ b/release/Release
@@ -0,0 +1,8 @@
+Origin: Needle Agent
+Label: Needle Agent
+Suite: stable
+Version: 1.0
+Codename: agent
+Architectures: iphoneos-arm
+Components: main
+Description: Needle Agent
\ No newline at end of file
diff --git a/release/dpkg-gettext.pl b/release/dpkg-gettext.pl
new file mode 100755
index 0000000..6bbfb3c
--- /dev/null
+++ b/release/dpkg-gettext.pl
@@ -0,0 +1,37 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+# Copied from /usr/share/perl5/Debconf/Gettext.pm
+
+use strict;
+
+BEGIN {
+ eval 'use Locale::gettext';
+ if ($@) {
+ eval q{
+ sub _g {
+ return shift;
+ }
+ sub textdomain {
+ }
+ sub ngettext {
+ if ($_[2] == 1) {
+ return $_[0];
+ } else {
+ return $_[1];
+ }
+ }
+ };
+ } else {
+ eval q{
+ sub _g {
+ return gettext(shift);
+ }
+ };
+ }
+}
+
+use base qw(Exporter);
+our @EXPORT=qw(_g textdomain ngettext);
+
+1;
diff --git a/release/dpkg-scanpackages.pl b/release/dpkg-scanpackages.pl
new file mode 100755
index 0000000..5168cf0
--- /dev/null
+++ b/release/dpkg-scanpackages.pl
@@ -0,0 +1,292 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use IO::Handle;
+use IO::File;
+
+my $version="1.13.25"; # This line modified by Makefile
+my $dpkglibdir="/usr/lib/dpkg"; # This line modified by Makefile
+
+($0) = $0 =~ m:.*/(.+):;
+
+push(@INC,$dpkglibdir);
+require 'dpkg-gettext.pl';
+textdomain("dpkg-dev");
+
+my %kmap= (optional => 'suggests',
+ recommended => 'recommends',
+ class => 'priority',
+ package_revision => 'revision',
+ );
+
+my @fieldpri= ('Package',
+ 'Source',
+ 'Version',
+ 'Priority',
+ 'Section',
+ 'Essential',
+ 'Maintainer',
+ 'Pre-Depends',
+ 'Depends',
+ 'Recommends',
+ 'Suggests',
+ 'Conflicts',
+ 'Provides',
+ 'Replaces',
+ 'Enhances',
+ 'Architecture',
+ 'Filename',
+ 'Size',
+ 'Installed-Size',
+ 'MD5sum',
+ 'Description',
+ 'Origin',
+ 'Bugs',
+ 'Name',
+ 'Author',
+ 'Homepage',
+ 'Website',
+ 'Depiction',
+ 'Icon'
+ );
+
+# This maps the fields into the proper case
+my %field_case;
+@field_case{map{lc($_)} @fieldpri} = @fieldpri;
+
+use Getopt::Long qw(:config bundling);
+
+my %options = (help => sub { &usage; exit 0; },
+ version => \&version,
+ udeb => 0,
+ arch => undef,
+ multiversion => 0,
+ );
+
+my $result = GetOptions(\%options,'help|h|?','version','udeb|u!','arch|a=s','multiversion|m!');
+
+sub version {
+ printf _g("Debian %s version %s.\n"), $0, $version;
+ exit;
+}
+
+sub usage {
+ printf _g(
+"Usage: %s [