THRIFT-3859: Add support for Unix Domain Sockets to TSocketServer and TSocketTransport.
Client: cocoa

TSocketServer and TSocketTransport have been refactored to support sockets created using either a port or a path.
Existing behavior for port-based socket transport is unchanged by this commit.

This closes #1031
diff --git a/lib/cocoa/src/server/TSocketServer.h b/lib/cocoa/src/server/TSocketServer.h
index fe657ea..95b0d3c 100644
--- a/lib/cocoa/src/server/TSocketServer.h
+++ b/lib/cocoa/src/server/TSocketServer.h
@@ -41,6 +41,10 @@
              protocolFactory:(id <TProtocolFactory>)protocolFactory
             processorFactory:(id <TProcessorFactory>)processorFactory;
 
+- (instancetype) initWithPath: (NSString *) path
+              protocolFactory: (id <TProtocolFactory>) protocolFactory
+             processorFactory: (id <TProcessorFactory>) processorFactory;
+
 @end
 
 
diff --git a/lib/cocoa/src/server/TSocketServer.m b/lib/cocoa/src/server/TSocketServer.m
index ccbbba1..09b603c 100644
--- a/lib/cocoa/src/server/TSocketServer.m
+++ b/lib/cocoa/src/server/TSocketServer.m
@@ -25,7 +25,7 @@
 
 #import <sys/socket.h>
 #include <netinet/in.h>
-
+#include <sys/un.h>
 
 
 NSString *const TSocketServerClientConnectionFinished = @"TSocketServerClientConnectionFinished";
@@ -40,13 +40,14 @@
 @property(strong, nonatomic) id<TProcessorFactory> processorFactory;
 @property(strong, nonatomic) NSFileHandle *socketFileHandle;
 @property(strong, nonatomic) dispatch_queue_t processingQueue;
+@property(strong, nonatomic) NSString *domainSocketPath;
 
 @end
 
 
 @implementation TSocketServer
 
--(instancetype) initWithPort:(int)port
+-(instancetype) initWithSocket:(CFSocketRef)socket
              protocolFactory:(id <TProtocolFactory>)protocolFactory
             processorFactory:(id <TProcessorFactory>)processorFactory;
 {
@@ -62,32 +63,7 @@
   _processingQueue = dispatch_queue_create("TSocketServer.processing", processingQueueAttr);
 
   // create a socket.
-  int fd = -1;
-  CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL);
-  if (socket) {
-    CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
-    fd = CFSocketGetNative(socket);
-    int yes = 1;
-    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
-
-    struct sockaddr_in addr;
-    memset(&addr, 0, sizeof(addr));
-    addr.sin_len = sizeof(addr);
-    addr.sin_family = AF_INET;
-    addr.sin_port = htons(port);
-    addr.sin_addr.s_addr = htonl(INADDR_ANY);
-    NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
-    if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
-      CFSocketInvalidate(socket);
-      CFRelease(socket);
-      NSLog(@"TSocketServer: Could not bind to address");
-      return nil;
-    }
-  }
-  else {
-    NSLog(@"TSocketServer: No server socket");
-    return nil;
-  }
+  int fd = CFSocketGetNative(socket);
 
   // wrap it in a file handle so we can get messages from it
   _socketFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd
@@ -106,15 +82,114 @@
   // tell socket to listen
   [_socketFileHandle acceptConnectionInBackgroundAndNotify];
 
-  NSLog(@"TSocketServer: Listening on TCP port %d", port);
+  return self;
+}
 
+- (id) initWithPort: (int) port
+    protocolFactory: (id <TProtocolFactory>) protocolFactory
+   processorFactory: (id <TProcessorFactory>) processorFactory
+{
+  CFSocketRef socket = [[self class] createSocketWithPort:port];
+  if (socket == NULL) {
+    return nil;
+  }
+
+  if (self = [self initWithSocket:socket protocolFactory:protocolFactory processorFactory:processorFactory]) {
+    NSLog(@"TSocketServer: Listening on TCP port %d", port);
+  }
   return self;
 }
 
 
++(CFSocketRef) createSocketWithPort:(int)port
+{
+  CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL);
+  if (socket) {
+    CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
+    int fd = CFSocketGetNative(socket);
+    int yes = 1;
+    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
+
+    struct sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_len = sizeof(addr);
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
+    if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
+      CFSocketInvalidate(socket);
+      CFRelease(socket);
+      NSLog(@"TSocketServer: Could not bind to address");
+      return NULL;
+    }
+
+    return socket;
+  }
+  else {
+    NSLog(@"TSocketServer: No server socket");
+    return NULL;
+  }
+}
+
+- (id) initWithPath: (NSString *) path
+    protocolFactory: (id <TProtocolFactory>) protocolFactory
+   processorFactory: (id <TProcessorFactory>) processorFactory
+{
+  _domainSocketPath = path;
+  CFSocketRef socket = [[self class] createSocketWithPath:path];
+  if (socket == NULL) {
+    return nil;
+  }
+
+  if (self = [self initWithSocket:socket protocolFactory:protocolFactory processorFactory:processorFactory]) {
+    NSLog(@"TSocketServer: Listening on path %@", path);
+  }
+  return self;
+}
+
++ (CFSocketRef) createSocketWithPath: (NSString *) path
+{
+  CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_LOCAL, SOCK_STREAM, IPPROTO_IP, 0, NULL, NULL);
+  if (socket) {
+    CFSocketSetSocketFlags(socket,  CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
+    int fd = CFSocketGetNative(socket);
+    int yes = 1;
+    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
+
+    size_t nullTerminatedPathLength = path.length + 1;
+    struct sockaddr_un addr;
+    if (nullTerminatedPathLength> sizeof(addr.sun_path)) {
+      NSLog(@"TSocketServer: Unable to create socket at path %@. Path is too long.", path);
+      return NULL;
+    }
+
+    addr.sun_family = AF_LOCAL;
+    memcpy(addr.sun_path, path.UTF8String, nullTerminatedPathLength);
+    addr.sun_len = SUN_LEN(&addr);
+
+    NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
+    if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
+      CFSocketInvalidate(socket);
+      CFRelease(socket);
+      NSLog(@"TSocketServer: Could not bind to address");
+      return NULL;
+    }
+
+    return socket;
+  } else {
+    NSLog(@"TSocketServer: No server socket");
+    return NULL;
+  }
+}
+
 -(void) dealloc
 {
   [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+  if (_domainSocketPath != nil) {
+    unlink(_domainSocketPath.UTF8String);
+  }
 }
 
 
diff --git a/lib/cocoa/src/transport/TSocketTransport.h b/lib/cocoa/src/transport/TSocketTransport.h
index 4ea03cc..a7b91a2 100644
--- a/lib/cocoa/src/transport/TSocketTransport.h
+++ b/lib/cocoa/src/transport/TSocketTransport.h
@@ -28,6 +28,8 @@
 -(id) initWithHostname:(NSString *)hostname
                   port:(int)port;
 
+-(id) initWithPath:(NSString *)path;
+
 @end
 
 
diff --git a/lib/cocoa/src/transport/TSocketTransport.m b/lib/cocoa/src/transport/TSocketTransport.m
index 272baf6..9c58abb 100644
--- a/lib/cocoa/src/transport/TSocketTransport.m
+++ b/lib/cocoa/src/transport/TSocketTransport.m
@@ -24,21 +24,21 @@
 #import <CFNetwork/CFNetwork.h>
 #endif
 
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
 @interface TSocketTransport () <NSStreamDelegate>
 @end
 
 
 @implementation TSocketTransport
 
--(id) initWithHostname:(NSString *)hostname
-                  port:(int)port
+- (id) initWithReadStream: (CFReadStreamRef) readStream writeStream: (CFWriteStreamRef) writeStream
 {
   NSInputStream *inputStream = nil;
   NSOutputStream *outputStream = nil;
 
-  CFReadStreamRef readStream = NULL;
-  CFWriteStreamRef writeStream = NULL;
-  CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)hostname, port, &readStream, &writeStream);
   if (readStream && writeStream) {
 
     CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
@@ -70,4 +70,62 @@
   return [super initWithInputStream:inputStream outputStream:outputStream];
 }
 
+- (id) initWithHostname: (NSString *) hostname
+                   port: (int) port
+{
+  CFReadStreamRef readStream = NULL;
+  CFWriteStreamRef writeStream = NULL;
+  CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)hostname, port, &readStream, &writeStream);
+  return [self initWithReadStream:readStream writeStream:writeStream];
+}
+
+- (id) initWithPath: (NSString *) path
+{
+  CFSocketNativeHandle sockfd = socket(AF_LOCAL, SOCK_STREAM, IPPROTO_IP);
+  int yes = 1;
+  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
+  {
+    NSLog(@"TSocketTransport: Unable to set REUSEADDR property of socket.");
+    return nil;
+  }
+
+  NSData *serverAddress = [[self class] createAddressWithPath:path];
+
+  CFReadStreamRef readStream = NULL;
+  CFWriteStreamRef writeStream = NULL;
+  CFStreamCreatePairWithSocket(kCFAllocatorDefault, sockfd, &readStream, &writeStream);
+  if (!readStream || !writeStream)
+  {
+    NSLog(@"TSocketTransport: Unable to create read/write stream pair for socket.");
+    return nil;
+  }
+
+  if (connect(sockfd, (struct sockaddr *)serverAddress.bytes, (socklen_t) serverAddress.length) < 0)
+  {
+    NSLog(@"TSocketTransport: Connect error: %s\n", strerror(errno));
+    return nil;
+  }
+
+  return [self initWithReadStream:readStream writeStream:writeStream];
+}
+
++ (NSData *) createAddressWithPath: (NSString *)path
+{
+  struct sockaddr_un servaddr;
+
+  size_t nullTerminatedPathLength = path.length + 1;
+  if (nullTerminatedPathLength> sizeof(servaddr.sun_path)) {
+    NSLog(@"TSocketTransport: Unable to create socket at path %@. Path is too long.", path);
+    return nil;
+  }
+
+  bzero(&servaddr,sizeof(servaddr));
+  servaddr.sun_family = AF_LOCAL;
+  memcpy(servaddr.sun_path, path.UTF8String, nullTerminatedPathLength);
+  servaddr.sun_len = SUN_LEN(&servaddr);
+
+  return [NSData dataWithBytes:&servaddr length:sizeof(servaddr)];
+}
+
+
 @end