/Pods/CocoaAsyncSocket/GCD/GCDAsyncSocket.m
Objective C | 7825 lines | 5189 code | 1691 blank | 945 comment | 1158 complexity | 1d291c75e68fd0db60f37271abe308b7 MD5 | raw file
Possible License(s): BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- //
- // 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 <CFNetwork/CFNetwork.h>
- #endif
- #import <arpa/inet.h>
- #import <fcntl.h>
- #import <ifaddrs.h>
- #import <netdb.h>
- #import <netinet/in.h>
- #import <net/if.h>
- #import <sys/socket.h>
- #import <sys/types.h>
- #import <sys/ioctl.h>
- #import <sys/poll.h>
- #import <sys/uio.h>
- #import <sys/un.h>
- #import <unistd.h>
- #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
- /**
- * Does ARC support support GCD objects?
- * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
- **/
- #if TARGET_OS_IPHONE
- // Compiling for iOS
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
- #define NEEDS_DISPATCH_RETAIN_RELEASE 0
- #else // iOS 5.X or earlier
- #define NEEDS_DISPATCH_RETAIN_RELEASE 1
- #endif
- #else
- // Compiling for Mac OS X
- #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
- #define NEEDS_DISPATCH_RETAIN_RELEASE 0
- #else
- #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
- #endif
- #endif
- #if 0
- // 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 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
- NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
- NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
- NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
- NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
- #if SECURE_TRANSPORT_MAYBE_AVAILABLE
- NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
- #if TARGET_OS_IPHONE
- NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
- NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
- #else
- NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
- #endif
- #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
- #if TARGET_OS_IPHONE
- kAddedStreamsToRunLoop = 1 << 16, // If set, CFStreams have been added to listener thread
- kUsingCFStreamForTLS = 1 << 17, // If set, we're forced to use CFStream instead of SecureTransport
- kSecureSocketHasBytesAvailable = 1 << 18, // 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
- #endif
- @interface GCDAsyncSocket (Private)
- // Accepting
- - (BOOL)doAccept:(int)socketFD;
- // Connecting
- - (void)startConnectTimeout:(NSTimeInterval)timeout;
- - (void)endConnectTimeout;
- - (void)doConnectTimeout;
- - (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port;
- - (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
- - (void)lookup:(int)aConnectIndex didFail:(NSError *)error;
- - (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr;
- - (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr;
- - (void)didConnect:(int)aConnectIndex;
- - (void)didNotConnect:(int)aConnectIndex error:(NSError *)error;
- // Disconnect
- - (void)closeWithError:(NSError *)error;
- - (void)close;
- - (void)maybeClose;
- // Errors
- - (NSError *)badConfigError:(NSString *)msg;
- - (NSError *)badParamError:(NSString *)msg;
- - (NSError *)gaiError:(int)gai_error;
- - (NSError *)errnoError;
- - (NSError *)errnoErrorWithReason:(NSString *)reason;
- - (NSError *)connectTimeoutError;
- - (NSError *)otherError:(NSString *)msg;
- // Diagnostics
- - (NSString *)connectedHost4;
- - (NSString *)connectedHost6;
- - (uint16_t)connectedPort4;
- - (uint16_t)connectedPort6;
- - (NSString *)localHost4;
- - (NSString *)localHost6;
- - (uint16_t)localPort4;
- - (uint16_t)localPort6;
- - (NSString *)connectedHostFromSocket4:(int)socketFD;
- - (NSString *)connectedHostFromSocket6:(int)socketFD;
- - (uint16_t)connectedPortFromSocket4:(int)socketFD;
- - (uint16_t)connectedPortFromSocket6:(int)socketFD;
- - (NSURL *)connectedUrlFromSocketUN:(int)socketFD;
- - (NSString *)localHostFromSocket4:(int)socketFD;
- - (NSString *)localHostFromSocket6:(int)socketFD;
- - (uint16_t)localPortFromSocket4:(int)socketFD;
- - (uint16_t)localPortFromSocket6:(int)socketFD;
- // Utilities
- - (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr
- address6:(NSMutableData **)addr6Ptr
- fromDescription:(NSString *)interfaceDescription
- port:(uint16_t)port;
- - (NSData *)getInterfaceAddressFromUrl:(NSURL *)url;
- - (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD;
- - (void)suspendReadSource;
- - (void)resumeReadSource;
- - (void)suspendWriteSource;
- - (void)resumeWriteSource;
- // Reading
- - (void)maybeDequeueRead;
- - (void)flushSSLBuffers;
- - (void)doReadData;
- - (void)doReadEOF;
- - (void)completeCurrentRead;
- - (void)endCurrentRead;
- - (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout;
- - (void)doReadTimeout;
- - (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
- // Writing
- - (void)maybeDequeueWrite;
- - (void)doWriteData;
- - (void)completeCurrentWrite;
- - (void)endCurrentWrite;
- - (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout;
- - (void)doWriteTimeout;
- - (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
- // Security
- - (void)maybeStartTLS;
- #if SECURE_TRANSPORT_MAYBE_AVAILABLE
- - (void)ssl_startTLS;
- - (void)ssl_continueSSLHandshake;
- #endif
- #if TARGET_OS_IPHONE
- - (void)cf_startTLS;
- #endif
- // CFStream
- #if TARGET_OS_IPHONE
- + (void)startCFStreamThreadIfNeeded;
- - (BOOL)createReadAndWriteStream;
- - (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite;
- - (BOOL)addStreamsToRunLoop;
- - (BOOL)openStreams;
- - (void)removeStreamsFromRunLoop;
- #endif
- // Class Methods
- + (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;
- + (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr;
- @end
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #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 = preBufferSize - (writePointer - readPointer);
-
- 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 = writePointer - readPointer;
- }
- - (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 - readPointer);
- }
- - (uint8_t *)writeBuffer
- {
- return writePointer;
- }
- - (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
- {
- if (bufferPtr) *bufferPtr = writePointer;
- if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer);
- }
- - (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
- - (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 NEEDS_DISPATCH_RETAIN_RELEASE
- if (dq) dispatch_retain(dq);
- #endif
-
- socket4FD = SOCKET_NULL;
- socket6FD = SOCKET_NULL;
- socketUN = SOCKET_NULL;
- socketUrl = nil;
- connectIndex = 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 NEEDS_DISPATCH_RETAIN_RELEASE
- dispatch_retain(sq);
- #endif
- }
- else
- {
- socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
- }
-
- readQueue = [[NSMutableArray alloc] initWithCapacity:5];
- currentRead = nil;
-
- writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
- currentWrite = nil;
-
- preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
- }
- return self;
- }
- - (void)dealloc
- {
- LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- [self closeWithError:nil];
- }
- else
- {
- dispatch_sync(socketQueue, ^{
- [self closeWithError:nil];
- });
- }
-
- delegate = nil;
-
- #if NEEDS_DISPATCH_RETAIN_RELEASE
- if (delegateQueue) dispatch_release(delegateQueue);
- #endif
- delegateQueue = NULL;
-
- #if NEEDS_DISPATCH_RETAIN_RELEASE
- if (socketQueue) dispatch_release(socketQueue);
- #endif
- socketQueue = NULL;
-
- LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark Configuration
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- - (id)delegate
- {
- if (dispatch_get_current_queue() == socketQueue)
- {
- 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_current_queue() == socketQueue) {
- 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_current_queue() == socketQueue)
- {
- 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 NEEDS_DISPATCH_RETAIN_RELEASE
- if (delegateQueue) dispatch_release(delegateQueue);
- if (newDelegateQueue) dispatch_retain(newDelegateQueue);
- #endif
-
- delegateQueue = newDelegateQueue;
- };
-
- if (dispatch_get_current_queue() == socketQueue) {
- 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_current_queue() == socketQueue)
- {
- 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 NEEDS_DISPATCH_RETAIN_RELEASE
- if (delegateQueue) dispatch_release(delegateQueue);
- if (newDelegateQueue) dispatch_retain(newDelegateQueue);
- #endif
-
- delegateQueue = newDelegateQueue;
- };
-
- if (dispatch_get_current_queue() == socketQueue) {
- 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)autoDisconnectOnClosedReadStream
- {
- // Note: YES means kAllowHalfDuplexConnection is OFF
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- return ((config & kAllowHalfDuplexConnection) == 0);
- }
- else
- {
- __block BOOL result;
-
- dispatch_sync(socketQueue, ^{
- result = ((config & kAllowHalfDuplexConnection) == 0);
- });
-
- return result;
- }
- }
- - (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
- {
- // Note: YES means kAllowHalfDuplexConnection is OFF
-
- dispatch_block_t block = ^{
-
- if (flag)
- config &= ~kAllowHalfDuplexConnection;
- else
- config |= kAllowHalfDuplexConnection;
- };
-
- if (dispatch_get_current_queue() == socketQueue)
- block();
- else
- dispatch_async(socketQueue, block);
- }
- - (BOOL)isIPv4Enabled
- {
- // Note: YES means kIPv4Disabled is OFF
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- 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_current_queue() == socketQueue)
- block();
- else
- dispatch_async(socketQueue, block);
- }
- - (BOOL)isIPv6Enabled
- {
- // Note: YES means kIPv6Disabled is OFF
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- 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_current_queue() == socketQueue)
- block();
- else
- dispatch_async(socketQueue, block);
- }
- - (BOOL)isIPv4PreferredOverIPv6
- {
- // Note: YES means kPreferIPv6 is OFF
-
- if (dispatch_get_current_queue() == socketQueue)
- {
- return ((config & kPreferIPv6) == 0);
- }
- else
- {
- __block BOOL result;
-
- dispatch_sync(socketQueue, ^{
- result = ((config & kPreferIPv6) == 0);
- });
-
- return result;
- }
- }
- - (void)setPreferIPv4OverIPv6:(BOOL)flag
- {
- // Note: YES means kPreferIPv6 is OFF
-
- dispatch_block_t block = ^{
-
- if (flag)
- config &= ~kPreferIPv6;
- else
- config |= kPreferIPv6;
- };
-
- if (dispatch_get_current_queue() == socketQueue)
- block();
- else
- dispatch_async(socketQueue, block);
- }
- - (id)userData
- {
- __block id result = nil;
-
- dispatch_block_t block = ^{
-
- result = userData;
- };
-
- if (dispatch_get_current_queue() == socketQueue)
- block();
- else
- dispatch_sync(socketQueue, block);
-
- return result;
- }
- - (void)setUserData:(id)arbitraryUserData
- {
- dispatch_block_t block = ^{
-
- if (userData != arbitraryUserData)
- {
- userData = arbitraryUserData;
- }
- };
-
- if (dispatch_get_current_queue() == socketQueue)
- 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
-
- const struct sockaddr *addr = (const struct sockaddr *)[interfaceAddr bytes];
- status = bind(socketFD, addr, addr->sa_len);
- 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;
-
- dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
-
- LogVerbose(@"event4Block");
-
- 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(accept4Source, ^{
-
- #if NEEDS_DISPATCH_RETAIN_RELEASE
- LogVerbose(@"dispatch_release(accept4Source)");
- dispatch_release(acceptSource);
- #endif
-
- LogVerbose(@"close(socket4FD)");
- close(socketFD);
- });
-
- 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;
-
- dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool {
-
- LogVerbose(@"event6Block");
-
- 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(accept6Source, ^{
-
- #if NEEDS_DISPATCH_RETAIN_RELEASE
- LogVerbose(@"dispatch_release(accept6Source)");
- dispatch_release(acceptSource);
- #endif
-
- LogVerbose(@"close(socket6FD)");
- close(socketFD);
- });
-
- LogVerbose(@"dispatch_resume(accept6Source)");
- dispatch_resume(accept6Source);
- }
-
- flags |= kSocketStarted;
-
- result = YES;
- }};
-
- if (dispatch_get_current_queue() == socketQueue)
- 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…
Large files files are truncated, but you can click here to view the full file