Project import
diff --git a/CFSockets/CFSocket.h b/CFSockets/CFSocket.h
new file mode 100755
index 0000000..910f596
--- /dev/null
+++ b/CFSockets/CFSocket.h
@@ -0,0 +1,181 @@
+// CFSockets CFSocket.h
+//
+// Copyright © 2009–2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+//------------------------------------------------------------------------------
+
+#import <Foundation/Foundation.h>
+
+@class CFSocket;
+@class CFStreamPair;
+
+@protocol CFSocketDelegate<NSObject>
+@optional
+
+- (void)socket:(CFSocket *)socket acceptNativeHandle:(NSSocketNativeHandle)nativeHandle;
+- (void)socket:(CFSocket *)socket acceptStreamPair:(CFStreamPair *)streamPair;
+
+@end
+
+/**
+ * Wraps Core Foundation's sockets in Objective-C clothing.
+ *
+ * Handles ARC memory management issues for a socket and implements a
+ * delegation protocol. As an object, you can sub-class the socket. It papers
+ * over the toll-free bridging requirements necessary for interactions between
+ * Core Foundation and Foundation. Start by initialising a socket, either an
+ * explicit IPv6 or IPv4 TCP socket, or whichever version of TCP socket the
+ * system makes available to you. Note that IPv4 clients can also access IPv6
+ * sockets.
+ *
+ * Note, you can have an object class called CFSocket; it does not clash with
+ * Apple's Core Foundation C-based socket functions, externals and constants
+ * because those exist in the C name space, while CFSocket here exists in the
+ * Objective-C name space. They do not collide.
+ */
+@interface CFSocket : NSObject
+{
+ CFSocketRef _socket;
+ CFRunLoopSourceRef _runLoopSource;
+}
+
+@property(weak, NS_NONATOMIC_IOSONLY) id<CFSocketDelegate> delegate;
+
+/**
+ * Designated initialiser.
+ *
+ * Initialisers also create the underlying Core Foundation socket. You
+ * cannot have a partially initialised Objective-C socket. When socket creation
+ * fails, initialisation fails also. All socket initialisers follow this
+ * pattern. Hence, you cannot initialise a socket with a `NULL` socket
+ * reference. In such cases, the initialiser answers `nil`.
+ *
+ * This approach creates a slight quandary. Creating a Core Foundation socket
+ * requires a socket context. The context needs to retain a bridging reference
+ * to `self`, the Objective-C object encapsulating the socket. Otherwise, the
+ * socket call-back function cannot springboard from C to Objective-C when
+ * call-backs trigger. When the initialiser returns successfully however, the
+ * answer overwrites `self`. What if `self` changes? If it changes to `nil`,
+ * no problem. But what if it changes to some other pointer address?
+ *
+ * TODO: Add more initialisers; specifically, socket signature initialisers.
+ *
+ * @param socket Reference to a CFSocket object.
+ */
+- (id)initWithSocketRef:(CFSocketRef)socket;
+
+- (id)initWithProtocolFamily:(int)family socketType:(int)type protocol:(int)protocol;
+- (id)initForTCPv6;
+- (id)initForTCPv4;
+
+/**
+ * Instantiates and creates a TCP socket using Internet Protocol version
+ * 6 if available, else tries version 4.
+ *
+ * Starts by creating a socket for Internet Protocol version 6. This
+ * also supports IPv4 clients via IPv4-mapped IPv6 addresses. Falls back to IPv4
+ * only when IPv6 fails.
+ *
+ * @result Answers a TCP socket, version 6 or 4; returns `nil` if the socket
+ * fails to create.
+ */
+- (id)initForTCP;
+
+/**
+ * Initialises using a native socket handle.
+ * @param nativeHandle Platform-specific native socket handle.
+ */
+- (id)initWithNativeHandle:(NSSocketNativeHandle)nativeHandle;
+
+/**
+ * Binds an address to a socket.
+ *
+ * Despite the innocuous-sounding method name, this method irreversibly
+ * binds the socket; assuming success. Do this early when constructing a
+ * socket. Be aware that accessing the address also binds the socket, if not
+ * already bound. You cannot therefore subsequently bind it. If you want to bind
+ * to a specific port, do so by setting the socket address _before_ asking for
+ * the address; that is, before using the getter method.
+ */
+- (BOOL)setAddress:(NSData *)addressData error:(NSError **)outError;
+
+- (BOOL)connectToAddress:(NSData *)addressData timeout:(NSTimeInterval)timeout error:(NSError **)outError;
+
+- (void)invalidate;
+- (BOOL)isValid;
+- (NSData *)address;
+- (NSData *)peerAddress;
+- (NSSocketNativeHandle)nativeHandle;
+- (BOOL)setReuseAddressOption:(BOOL)flag;
+
+/**
+ * Answers the socket address family.
+ *
+ * For Internet-based sockets, answers either `AF_INET6` or
+ * `AF_INET`. The latter for IP version 4 addresses. Note, protocol and address
+ * family are one and the same for Internet addresses. Protocol families are
+ * defined in terms of their address family; the `PF_` equals its corresponding
+ * `AF_` manifest constant.
+ *
+ * You can use this for polymorphic behaviour. If behaviour depends on a
+ * particular kind of socket, you can ask this method for the underlying address
+ * family and respond accordingly. This method differs from -address which binds
+ * the address first. The implementation here obtains the address family
+ * non-destructively; socket state remains unchanged.
+ *
+ * @result Answers the socket address family, or `AF_MAX` when an error
+ * occurs. On error, standard library `errno` value indicates the problem.
+ */
+- (int)addressFamily;
+
+/**
+ * Answers the port number.
+ *
+ * The answer depends on the socket's address family: version 4 or
+ * 6. Handles the network-to-host byte ordering. The resulting integer has
+ * correct ordering for the host machine.
+ */
+- (int)port;
+
+- (void)addToCurrentRunLoopForCommonModes;
+- (void)removeFromCurrentRunLoopForCommonModes;
+- (void)disableAcceptCallBack;
+- (void)enableAcceptCallBack;
+
+/**
+ * Handles the socket _accept_ call-back behaviour.
+ *
+ * Executes when Next Step meets Core Foundation. The argument specifies a
+ * native socket handle, an integer defining the Unix socket descriptor.
+ *
+ * Exists to allow for optional overriding. You do not need to deploy
+ * the delegate protocol if your sub-class handles "accept native handle" events
+ * directly; though delegation usually works best.
+ *
+ * @param nativeHandle Platform-specific native socket handle.
+ */
+- (void)acceptNativeHandle:(NSSocketNativeHandle)nativeHandle;
+
+@end
+
+extern NSString *const CFSocketErrorDomain;
+
+void __CFSocketCallOut(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info);
diff --git a/CFSockets/CFSocket.m b/CFSockets/CFSocket.m
new file mode 100755
index 0000000..4638696
--- /dev/null
+++ b/CFSockets/CFSocket.m
@@ -0,0 +1,270 @@
+// CFSockets CFSocket.m
+//
+// Copyright © 2009–2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+//------------------------------------------------------------------------------
+
+#import "CFSocket.h"
+#import "CFStreamPair.h"
+
+// for setsockopt(2)
+#import <sys/socket.h>
+
+// for IPPROTO_TCP
+#import <netinet/in.h>
+
+@implementation CFSocket
+
+@synthesize delegate = _delegate;
+
+- (id)init
+{
+ return (self = nil);
+}
+
+// designated initialiser
+- (id)initWithSocketRef:(CFSocketRef)socket
+{
+ if ((self = [super init]))
+ {
+ if (socket)
+ {
+ _socket = socket;
+ }
+ else
+ {
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (id)initWithProtocolFamily:(int)family socketType:(int)type protocol:(int)protocol
+{
+ CFSocketContext context = { .info = (__bridge void *)self };
+ return [self initWithSocketRef:CFSocketCreate(kCFAllocatorDefault, family, type, protocol, kCFSocketAcceptCallBack, __CFSocketCallOut, &context)];
+}
+
+- (id)initForTCPv6
+{
+ return [self initWithProtocolFamily:PF_INET6 socketType:SOCK_STREAM protocol:IPPROTO_TCP];
+}
+
+- (id)initForTCPv4
+{
+ return [self initWithProtocolFamily:PF_INET socketType:SOCK_STREAM protocol:IPPROTO_TCP];
+}
+
+- (id)initForTCP
+{
+ self = [self initForTCPv6];
+ if (self == nil) self = [self initForTCPv4];
+ return self;
+}
+
+- (id)initWithNativeHandle:(NSSocketNativeHandle)nativeHandle
+{
+ CFSocketContext context = { .info = (__bridge void *)self };
+ return [self initWithSocketRef:CFSocketCreateWithNative(kCFAllocatorDefault, nativeHandle, kCFSocketAcceptCallBack, __CFSocketCallOut, &context)];
+}
+
+- (BOOL)setAddress:(NSData *)addressData error:(NSError **)outError
+{
+ CFSocketError error = CFSocketSetAddress(_socket, (__bridge CFDataRef)addressData);
+ BOOL success = (error == kCFSocketSuccess);
+ if (!success)
+ {
+ if (outError && *outError == nil)
+ {
+ *outError = [NSError errorWithDomain:CFSocketErrorDomain code:error userInfo:nil];
+ }
+ }
+ return success;
+}
+
+- (BOOL)connectToAddress:(NSData *)addressData timeout:(NSTimeInterval)timeout error:(NSError **)outError
+{
+ CFSocketError error = CFSocketConnectToAddress(_socket, (__bridge CFDataRef)addressData, timeout);
+ BOOL success = (error == kCFSocketSuccess);
+ if (!success)
+ {
+ if (outError && *outError == nil)
+ {
+ *outError = [NSError errorWithDomain:CFSocketErrorDomain code:error userInfo:nil];
+ }
+ }
+ return success;
+}
+
+- (void)invalidate
+{
+ // Never close the underlying native socket without first invalidating.
+ CFSocketInvalidate(_socket);
+}
+
+- (BOOL)isValid
+{
+ return CFSocketIsValid(_socket) != false;
+}
+
+- (NSData *)address
+{
+ return CFBridgingRelease(CFSocketCopyAddress(_socket));
+}
+
+- (NSData *)peerAddress
+{
+ return CFBridgingRelease(CFSocketCopyPeerAddress(_socket));
+}
+
+- (NSSocketNativeHandle)nativeHandle
+{
+ return CFSocketGetNative(_socket);
+}
+
+- (BOOL)setReuseAddressOption:(BOOL)flag
+{
+ int option = (flag == NO) ? 0 : 1;
+ return 0 == setsockopt([self nativeHandle], SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option));
+}
+
+- (int)addressFamily
+{
+ uint8_t sockaddr[SOCK_MAXADDRLEN];
+ socklen_t len = sizeof(sockaddr);
+ return 0 == getsockname([self nativeHandle], (struct sockaddr *)sockaddr, &len) && len >= offsetof(struct sockaddr, sa_data) ? ((struct sockaddr *)sockaddr)->sa_family : AF_MAX;
+}
+
+- (int)port
+{
+ int port;
+ switch ([self addressFamily])
+ {
+ case AF_INET:
+ port = ntohs(((struct sockaddr_in *)[[self address] bytes])->sin_port);
+ break;
+ case AF_INET6:
+ port = ntohs(((struct sockaddr_in6 *)[[self address] bytes])->sin6_port);
+ break;
+ default:
+ port = 0;
+ }
+ return port;
+}
+
+- (void)addToCurrentRunLoopForCommonModes
+{
+ // NSRunLoop is not toll-free bridged to CFRunLoop, even though their names
+ // might suggest that they are.
+ if (_runLoopSource == NULL)
+ {
+ _runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
+ }
+}
+
+- (void)removeFromCurrentRunLoopForCommonModes
+{
+ if (_runLoopSource)
+ {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
+ CFRelease(_runLoopSource);
+ _runLoopSource = NULL;
+ }
+}
+
+- (void)disableAcceptCallBack
+{
+ CFSocketDisableCallBacks(_socket, kCFSocketAcceptCallBack);
+}
+
+- (void)enableAcceptCallBack
+{
+ // The Read, Accept and Data callbacks are mutually exclusive.
+ CFSocketEnableCallBacks(_socket, kCFSocketAcceptCallBack);
+}
+
+- (void)acceptNativeHandle:(NSSocketNativeHandle)nativeHandle
+{
+ id<CFSocketDelegate> delegate = [self delegate];
+ if (delegate)
+ {
+ if ([delegate respondsToSelector:@selector(socket:acceptNativeHandle:)])
+ {
+ [delegate socket:self acceptNativeHandle:nativeHandle];
+ }
+ else if ([delegate respondsToSelector:@selector(socket:acceptStreamPair:)])
+ {
+ CFStreamPair *streamPair = [[CFStreamPair alloc] initWithSocketNativeHandle:nativeHandle];
+ if (streamPair)
+ {
+ [delegate socket:self acceptStreamPair:streamPair];
+ }
+ }
+ else
+ {
+ close(nativeHandle);
+ }
+ }
+ else
+ {
+ close(nativeHandle);
+ }
+}
+
+- (void)dealloc
+{
+ // The de-allocator does not need to wonder if the underlying socket exists,
+ // or not. By contract, the socket must exist. This assumes, of course, that
+ // a failed initialisation sequence does not invoke the
+ // de-allocator. However, you cannot assume that. Assigning self to nil
+ // under ARC de-allocates the instance and invokes the -dealloc method.
+ [self removeFromCurrentRunLoopForCommonModes];
+ if (_socket)
+ {
+ CFRelease(_socket);
+ _socket = NULL;
+ }
+}
+
+@end
+
+NSString *const CFSocketErrorDomain = @"CFSocketErrorDomain";
+
+void __CFSocketCallOut(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
+{
+ switch (type)
+ {
+ case kCFSocketAcceptCallBack:
+ {
+ // Next Step meets Core Foundation socket native handle type in the
+ // next statement. You can use them interchangeably. Apple
+ // type-define both as int. They are really Unix socket
+ // descriptors. The external interface uses the Next Step
+ // definition, since the Next Step foundation framework is the most
+ // immediate dependency.
+ [(__bridge CFSocket *)info acceptNativeHandle:*(CFSocketNativeHandle *)data];
+ break;
+ }
+ default:
+ ;
+ }
+}
diff --git a/CFSockets/CFSocketAddressDataHelpers.h b/CFSockets/CFSocketAddressDataHelpers.h
new file mode 100755
index 0000000..44369fc
--- /dev/null
+++ b/CFSockets/CFSocketAddressDataHelpers.h
@@ -0,0 +1,35 @@
+/* CFSockets CFSocketAddressDataHelpers.h
+ *
+ * Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ ******************************************************************************/
+
+#import <Foundation/Foundation.h>
+
+#import <netinet/in.h>
+
+NSData *CFSocketAddressDataFromIPv6AddressWithPort(const struct in6_addr *addr, in_port_t port);
+NSData *CFSocketAddressDataFromAnyIPv6WithPort(in_port_t port);
+NSData *CFSocketAddressDataFromLoopBackIPv6WithPort(in_port_t port);
+
+NSData *CFSocketAddressDataFromIPv4AddressWithPort(in_addr_t addr, in_port_t port);
+NSData *CFSocketAddressDataFromAnyIPv4WithPort(in_port_t port);
+NSData *CFSocketAddressDataFromLoopBackIPv4WithPort(in_port_t port);
diff --git a/CFSockets/CFSocketAddressDataHelpers.m b/CFSockets/CFSocketAddressDataHelpers.m
new file mode 100644
index 0000000..a054c7d
--- /dev/null
+++ b/CFSockets/CFSocketAddressDataHelpers.m
@@ -0,0 +1,72 @@
+/* CFSockets CFSocketAddressDataHelpers.m
+ *
+ * Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ ******************************************************************************/
+
+#import "CFSocketAddressDataHelpers.h"
+#import "arpa/inet.h"
+
+NSData *CFSocketAddressDataFromIPv6AddressWithPort(const struct in6_addr *addr, in_port_t port)
+{
+ struct sockaddr_in6 sockaddr;
+ memset(&sockaddr, 0, sizeof(sockaddr));
+
+ sockaddr.sin6_len = sizeof(sockaddr);
+ sockaddr.sin6_family = AF_INET6;
+ sockaddr.sin6_port = htons(port);
+ memcpy(&sockaddr.sin6_addr, addr, sizeof(sockaddr.sin6_addr));
+
+ return [NSData dataWithBytes:&sockaddr length:sizeof(sockaddr)];
+}
+
+NSData *CFSocketAddressDataFromAnyIPv6WithPort(in_port_t port)
+{
+ return CFSocketAddressDataFromIPv6AddressWithPort(&in6addr_any, port);
+}
+
+NSData *CFSocketAddressDataFromLoopBackIPv6WithPort(in_port_t port)
+{
+ return CFSocketAddressDataFromIPv6AddressWithPort(&in6addr_loopback, port);
+}
+
+NSData *CFSocketAddressDataFromIPv4AddressWithPort(in_addr_t addr, in_port_t port)
+{
+ struct sockaddr_in sockaddr;
+ memset(&sockaddr, 0, sizeof(sockaddr));
+
+ sockaddr.sin_len = sizeof(sockaddr);
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_port = htons(port);
+ sockaddr.sin_addr.s_addr = htonl(addr);
+
+ return [NSData dataWithBytes:&sockaddr length:sizeof(sockaddr)];
+}
+
+NSData *CFSocketAddressDataFromAnyIPv4WithPort(in_port_t port)
+{
+ return CFSocketAddressDataFromIPv4AddressWithPort(INADDR_ANY, port);
+}
+
+NSData *CFSocketAddressDataFromLoopBackIPv4WithPort(in_port_t port)
+{
+ return CFSocketAddressDataFromIPv4AddressWithPort(INADDR_LOOPBACK, port);
+}
diff --git a/CFSockets/CFSockets.h b/CFSockets/CFSockets.h
new file mode 100755
index 0000000..58aa56b
--- /dev/null
+++ b/CFSockets/CFSockets.h
@@ -0,0 +1,28 @@
+/* CFSockets CFSockets.h
+ *
+ * Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ ******************************************************************************/
+
+#import <CFSockets/CFSocket.h>
+#import <CFSockets/CFSocketAddressDataHelpers.h>
+#import <CFSockets/CFStreamPair.h>
+#import <CFSockets/Versioning.h>
diff --git a/CFSockets/CFStreamPair.h b/CFSockets/CFStreamPair.h
new file mode 100755
index 0000000..7d6e5ea
--- /dev/null
+++ b/CFSockets/CFStreamPair.h
@@ -0,0 +1,100 @@
+// CFSockets CFStreamPair.h
+//
+// Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+//------------------------------------------------------------------------------
+
+#import <Foundation/Foundation.h>
+
+@class CFStreamPair;
+
+@protocol CFStreamPairDelegate<NSObject>
+@optional
+
+- (void)streamPair:(CFStreamPair *)streamPair hasBytesAvailable:(NSUInteger)bytesAvailable;
+
+- (void)streamPair:(CFStreamPair *)streamPair handleRequestEvent:(NSStreamEvent)eventCode;
+- (void)streamPair:(CFStreamPair *)streamPair handleResponseEvent:(NSStreamEvent)eventCode;
+
+@end
+
+/**
+ * Adds request-response semantics to a socket by encapsulating it with
+ * an input-output stream pair.
+ *
+ * For TCP connections, the request input stream corresponds to the
+ * request; _receive_ the request bytes from the request input, _send_ the
+ * response to the response output stream.
+ */
+@interface CFStreamPair : NSObject<NSStreamDelegate>
+
+@property(weak, NS_NONATOMIC_IOSONLY) id<CFStreamPairDelegate> delegate;
+@property(strong, NS_NONATOMIC_IOSONLY) NSInputStream *requestStream;
+@property(strong, NS_NONATOMIC_IOSONLY) NSOutputStream *responseStream;
+
+- (id)initWithRequestStream:(NSInputStream *)requestStream responseStream:(NSOutputStream *)responseStream;
+- (id)initWithSocketNativeHandle:(NSSocketNativeHandle)socketNativeHandle;
+
+/**
+ * Opens the request and response streams, scheduling them for service within
+ * the current run loop. You cannot reopen the streams.
+ *
+ * This method assumes that you have not already delegated, scheduled
+ * or opened the underlying request-response stream pair.
+ */
+- (void)open;
+
+/**
+ * Reverses the opening. Closes the request and response streams, removes them
+ * from the current run loop. This assumes that you send `-close` from the same
+ * thread as you sent the original `-open`.
+ */
+- (void)close;
+
+/**
+ * Destructively receives bytes from the request buffer.
+ */
+- (NSData *)receiveAvailableBytes;
+
+/**
+ * Special convenience method for receiving lines of text from the
+ * request stream based on a given string encoding.
+ *
+ * The result includes any line termination characters. There could be
+ * more than one termination character at the end of the line since some line
+ * termination sequences span multiple characters.
+ *
+ * @result Answers `nil` if the request buffer does not yet contain a complete
+ * line. Try again later.
+ */
+- (NSString *)receiveLineUsingEncoding:(NSStringEncoding)encoding;
+
+- (void)sendBytes:(NSData *)outputBytes;
+
+//-------------------------------------------------------------------- Overrides
+
+- (void)hasBytesAvailable;
+- (void)hasSpaceAvailable;
+- (void)sendBytes;
+- (void)handleRequestEvent:(NSStreamEvent)eventCode;
+- (void)handleResponseEvent:(NSStreamEvent)eventCode;
+
+@end
diff --git a/CFSockets/CFStreamPair.m b/CFSockets/CFStreamPair.m
new file mode 100644
index 0000000..2b6a122
--- /dev/null
+++ b/CFSockets/CFStreamPair.m
@@ -0,0 +1,338 @@
+// CFSockets CFStreamPair.m
+//
+// Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+//------------------------------------------------------------------------------
+
+#import "CFStreamPair.h"
+
+static NSString *const NewLineCharacter = @"\n";
+
+@interface CFStreamPair()
+
+// You would not normally access the buffers directly. The following exposes the
+// buffer implementation, albeit away from the public header: just a pair of
+// mutable data objects.
+@property(strong, NS_NONATOMIC_IOSONLY) NSMutableData *requestBuffer;
+@property(strong, NS_NONATOMIC_IOSONLY) NSMutableData *responseBuffer;
+
+@end
+
+@implementation CFStreamPair
+
+@synthesize delegate = _delegate;
+
+// streams
+@synthesize requestStream = _requestStream;
+@synthesize responseStream = _responseStream;
+
+// buffers
+@synthesize requestBuffer = _requestBuffer;
+@synthesize responseBuffer = _responseBuffer;
+
+// designated initialiser
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ // Sets up the request and response buffers at the outset. You can ask
+ // for available request bytes even before the request stream
+ // opens. Similarly, you can send response bytes even before the
+ // response opens. Hard to imagine exactly why however. Still, there is
+ // nothing to say that we can assume that the response stream will open
+ // before the request opens, or vice versa; indeed, the delegate may
+ // even respond by sending some bytes even before the response stream
+ // becomes ready. The buffer pair make such behaviour a valid pattern.
+ [self setRequestBuffer:[NSMutableData data]];
+ [self setResponseBuffer:[NSMutableData data]];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self close];
+}
+
+// convenience initialiser
+- (id)initWithRequestStream:(NSInputStream *)requestStream responseStream:(NSOutputStream *)responseStream
+{
+ self = [self init];
+ if (self)
+ {
+ [self setRequestStream:requestStream];
+ [self setResponseStream:responseStream];
+ }
+ return self;
+}
+
+- (id)initWithSocketNativeHandle:(NSSocketNativeHandle)socketNativeHandle
+{
+ if ((self = [self init]))
+ {
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+ CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketNativeHandle, &readStream, &writeStream);
+ if (readStream && writeStream)
+ {
+ CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
+ CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
+ [self setRequestStream:CFBridgingRelease(readStream)];
+ [self setResponseStream:CFBridgingRelease(writeStream)];
+ }
+ else
+ {
+ if (readStream) CFRelease(readStream);
+ if (writeStream) CFRelease(writeStream);
+
+ // Something went wrong. Answer nil. Bear in mind however that this
+ // does not mean that the de-allocation method will not run: it
+ // will run.
+ self = nil;
+ }
+ }
+ return self;
+}
+
+- (void)open
+{
+ for (NSStream *stream in [NSArray arrayWithObjects:[self requestStream], [self responseStream], nil])
+ {
+ [stream setDelegate:self];
+
+ [stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ [stream open];
+ }
+}
+
+- (void)close
+{
+ // Send -close first. Closing may trigger events. Let the stream emit all
+ // events until closing finishes. Might be wise to check the stream status
+ // first, before attempting to close the stream. The de-allocator invokes
+ // -close and therefore may send a double-close if the pair has already
+ // received an explicit -close message.
+ for (NSStream *stream in [NSArray arrayWithObjects:[self requestStream], [self responseStream], nil])
+ {
+ [stream close];
+ [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ }
+}
+
+- (NSData *)receiveAvailableBytes
+{
+ NSData *bytes = [[self requestBuffer] copy];
+ [[self requestBuffer] setLength:0];
+ return bytes;
+}
+
+- (NSString *)receiveLineUsingEncoding:(NSStringEncoding)encoding {
+ // The implementation first converts all the request bytes to a string. This
+ // could be risky for multi-byte characters. The implementation effectively
+ // assumes that multi-byte characters do not cross buffer boundaries.
+ //
+ // When the request range has length equal to zero, sending
+ // -lineRangeForRange: searches for the first line. The final bit is
+ // tricky. How to dissect the line from any remaining characters? Simply
+ // convert the remaining characters back to data using the given encoding.
+ NSString *result;
+
+ NSArray *requestsArray = [self requestsArrayFromData:self.requestBuffer withEncoding:encoding];
+ NSString *requestString = requestsArray.firstObject;
+
+ NSRange lineRange = [requestString lineRangeForRange:NSMakeRange(0, 0)];
+
+ if (lineRange.length) {
+ NSMutableArray *mutableRequestsArray = [requestsArray mutableCopy];
+ [mutableRequestsArray removeObjectAtIndex:0];
+
+ if (mutableRequestsArray.count) {
+ self.requestBuffer.data = [self dataFromRequestsArray:mutableRequestsArray withEncoding:encoding];
+ } else {
+ self.requestBuffer.length = 0;
+ }
+
+ result = [requestString substringToIndex:lineRange.length];
+ } else {
+ result = nil;
+ }
+
+ return result;
+}
+
+- (NSArray *)requestsArrayFromData:(NSData *)data withEncoding:(NSStringEncoding)encoding {
+ NSString *requestsString = [[NSString alloc] initWithData:data encoding:encoding];
+ NSMutableArray *requestsArray = [[requestsString componentsSeparatedByString:NewLineCharacter] mutableCopy];
+
+ for (NSInteger i = requestsArray.count - 1; i >= 0; i--) {
+ NSString *requestString = [((NSString *) requestsArray[i]) stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+
+ if (requestString.length == 0) {
+ [requestsArray removeObjectAtIndex:i];
+ }
+ }
+
+ return requestsArray;
+}
+
+- (NSData *)dataFromRequestsArray:(NSArray *)requestsArray withEncoding:(NSStringEncoding)encoding {
+ NSString *requestBufferString = [requestsArray componentsJoinedByString:NewLineCharacter];
+
+ return [requestBufferString dataUsingEncoding:encoding];
+}
+
+- (void)sendBytes:(NSData *)responseBytes
+{
+ [[self responseBuffer] appendData:responseBytes];
+
+ // Trigger a "has space available" event if the response stream reports
+ // available space at this point.
+ if ([[self responseStream] hasSpaceAvailable])
+ {
+ [self hasSpaceAvailable];
+ }
+}
+
+#pragma mark -
+#pragma mark Overrides
+
+- (void)hasBytesAvailable
+{
+ if ([[self requestBuffer] length])
+ {
+ id delegate = [self delegate];
+ if (delegate && [delegate respondsToSelector:@selector(streamPair:hasBytesAvailable:)])
+ {
+ [delegate streamPair:self hasBytesAvailable:[[self requestBuffer] length]];
+ }
+ }
+}
+
+- (void)hasSpaceAvailable
+{
+ // Note that writing zero bytes to the response stream closes the
+ // connection. Therefore, avoid sending nothing unless you want to close.
+ if ([[self responseBuffer] length])
+ {
+ [self sendBytes];
+ }
+}
+
+- (void)sendBytes
+{
+ NSMutableData *responseBuffer = [self responseBuffer];
+ NSInteger bytesSent = [[self responseStream] write:[responseBuffer bytes] maxLength:[responseBuffer length]];
+ if (bytesSent > 0)
+ {
+ [responseBuffer replaceBytesInRange:NSMakeRange(0, bytesSent) withBytes:NULL length:0];
+ }
+}
+
+- (void)handleRequestEvent:(NSStreamEvent)eventCode
+{
+ switch (eventCode)
+ {
+ case NSStreamEventHasBytesAvailable:
+ {
+ uint8_t bytes[4096];
+ NSInteger bytesAvailable = [[self requestStream] read:bytes maxLength:sizeof(bytes)];
+ // Do not send a -read:maxLength message unless the stream reports
+ // that it has bytes available. Always send this message at least
+ // once when the bytes-available event fires, i.e. right now. The
+ // stream event indicates that available bytes have already been
+ // sensed. Avoid asking again.
+ //
+ // What happens however if more bytes arrive while reading, or the
+ // available bytes overflow the stack-based temporary buffer? In
+ // these cases, after reading, ask if more bytes exist. Issue
+ // another read if they do, and repeat while they do.
+ while (bytesAvailable > 0)
+ {
+ [[self requestBuffer] appendBytes:bytes length:bytesAvailable];
+ if ([[self requestStream] hasBytesAvailable])
+ {
+ bytesAvailable = [[self requestStream] read:bytes maxLength:sizeof(bytes)];
+ }
+ else
+ {
+ bytesAvailable = 0;
+ }
+ }
+ // Please note, the delegate can receive an has-bytes-available
+ // event immediately followed by an error event.
+ [self hasBytesAvailable];
+ if (bytesAvailable < 0)
+ {
+
+ }
+ break;
+ }
+ default:
+ ;
+ }
+
+ id delegate = [self delegate];
+ if (delegate && [delegate respondsToSelector:@selector(streamPair:handleRequestEvent:)])
+ {
+ [delegate streamPair:self handleRequestEvent:eventCode];
+ }
+}
+
+- (void)handleResponseEvent:(NSStreamEvent)eventCode
+{
+ switch (eventCode)
+ {
+ case NSStreamEventHasSpaceAvailable:
+ {
+ [self hasSpaceAvailable];
+ break;
+ }
+ default:
+ ;
+ }
+
+ id delegate = [self delegate];
+ if (delegate && [delegate respondsToSelector:@selector(streamPair:handleResponseEvent:)])
+ {
+ [delegate streamPair:self handleResponseEvent:eventCode];
+ }
+}
+
+#pragma mark -
+#pragma mark Stream Delegate
+
+- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
+{
+ if (stream == [self requestStream])
+ {
+ [self handleRequestEvent:eventCode];
+ }
+ else if (stream == [self responseStream])
+ {
+ [self handleResponseEvent:eventCode];
+ }
+ else
+ {
+ ;
+ }
+}
+
+@end
diff --git a/CFSockets/Versioning.h b/CFSockets/Versioning.h
new file mode 100755
index 0000000..8d3e74e
--- /dev/null
+++ b/CFSockets/Versioning.h
@@ -0,0 +1,36 @@
+/* CFSockets Versioning.h
+ *
+ * Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ ******************************************************************************/
+
+#import <Foundation/Foundation.h>
+
+extern const unsigned char kCFSocketsVersionString[];
+extern const double kCFSocketsVersionNumber;
+
+/**
+ * Answers the current Apple-generic versioning-formatted version string. The
+ * version string has been trimmed. It has no leading or trailing whitespace or
+ * newlines. Note that the raw C-based version string contrastingly has a single
+ * terminating newline character.
+ */
+NSString *CFSocketsVersionString(void);
diff --git a/CFSockets/Versioning.m b/CFSockets/Versioning.m
new file mode 100755
index 0000000..94846c8
--- /dev/null
+++ b/CFSockets/Versioning.m
@@ -0,0 +1,38 @@
+/* CFSockets Versioning.m
+ *
+ * Copyright © 2012, 2013, Roy Ratcliffe, Pioneering Software, United Kingdom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
+ * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ ******************************************************************************/
+
+#import "Versioning.h"
+
+NSString *CFSocketsVersionString()
+{
+ // The implementation assumes that the raw C-language version string
+ // terminates with null. It also trims assuming that the very last character
+ // is a terminating line feed. Also assumes UTF-8 encoding.
+ static NSString *__strong versionString;
+ if (versionString == nil)
+ {
+ versionString = [[NSString stringWithCString:(const char *)kCFSocketsVersionString encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ }
+ return versionString;
+}
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..1b59196
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+# Core Foundation Sockets
+
+[](https://travis-ci.org/royratcliffe/CFSockets)
+
+The project has four targets: a Cocoa framework with its unit test bundle, an
+iOS static library with its unit test bundle. The framework and the library
+share the same source files. The sources use Automatic Reference Counting (ARC).
+
+Why the plural project name? The project goes by the name 'Core Foundation
+Sockets' rather than 'Core Foundation Socket.' This avoids confusion with
+`CFSocket` which represents just one component of the CFSockets project. The
+project's monolithic header imports as `<CFSockets/CFSockets.h>`. The
+additional _s_ for the project prevents a clash with header
+`<CFSockets/CFSocket.h>` which only imports the `CFSocket` class.