THRIFT-4838: Add unix socket support for Swift
Client: Swift
Patch: Kino Roy
diff --git a/lib/swift/Sources/LinuxHelper.swift b/lib/swift/Sources/LinuxHelper.swift
index 83603f8..7ab0b3e 100644
--- a/lib/swift/Sources/LinuxHelper.swift
+++ b/lib/swift/Sources/LinuxHelper.swift
@@ -27,7 +27,7 @@
   
 extension UInt {
   public static func &(lhs: UInt, rhs: Int) -> UInt {
-    let cast = unsafeBitCast(rhs, to: UInt.self)
+    let cast = UInt(bitPattern: rhs)
     return lhs & cast
   }
 }
diff --git a/lib/swift/Sources/TSocketServer.swift b/lib/swift/Sources/TSocketServer.swift
index 6cd9adf..27b4b55 100644
--- a/lib/swift/Sources/TSocketServer.swift
+++ b/lib/swift/Sources/TSocketServer.swift
@@ -102,6 +102,49 @@
     // tell socket to listen
     acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)
   }
+
+  public init(path: String,
+              inProtocol: InProtocol.Type,
+              outProtocol: OutProtocol.Type,
+              processor: Processor) throws {
+      self.processor = processor
+      // create a socket
+      let socket = UnixSocket(path: path)
+      let fd = socket.fd
+
+      if fd == -1 {
+          print("TSocketServer: No server socket")
+          throw TTransportError(error: .notOpen, message: "Could not create socket")
+      }
+
+      // wrap it in a file handle so we can get messages from it
+      socketFileHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
+
+      // register for notifications of accepted incoming connections
+      _ = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
+                                                 object: nil, queue: nil) {
+          [weak self] notification in
+          guard let strongSelf = self else { return }
+          guard let clientSocket = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
+          strongSelf.connectionAccepted(clientSocket)
+      }
+
+      let bindRes = socket.bind()
+      guard bindRes == 0 else {
+          print("TServerSocket: bind failed")
+          throw TTransportError(error: .notOpen, message: "Could not create socket")
+      }
+      let listenRes = listen(fd, 1024)
+      guard listenRes == 0 else {
+          print("TServerSocket: listen failed")
+          throw TTransportError(error: .notOpen, message: "Could not create socket")
+      }
+
+      // tell socket to listen
+      acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)
+
+      print("TSocketServer: Listening on unix path \(path)")
+  }
   
   private func acceptConnectionInBackgroundAndNotify(handle: FileHandle) {
     DispatchQueue(label: "TSocketServer.connectionAccept").async {
@@ -111,6 +154,7 @@
       }
     }
   }
+  
   func connectionAccepted(_ clientSocket: FileHandle) {
     // Now that we have a client connected, handle the request on queue
     processingQueue.async {
diff --git a/lib/swift/Sources/TSocketTransport.swift b/lib/swift/Sources/TSocketTransport.swift
index 640612b..fb99bc5 100644
--- a/lib/swift/Sources/TSocketTransport.swift
+++ b/lib/swift/Sources/TSocketTransport.swift
@@ -173,6 +173,15 @@
     
     self.init(socketDescriptor: sock)
   }
+
+  public convenience init(path: String) throws {
+    let socket = UnixSocket(path: path)
+    let errno = socket.connect()
+    guard errno == 0 else {
+      throw TTransportError(error: .notOpen, message: "Error binding to socket at path: \(path). Errno: \(errno)")
+    }
+    self.init(socketDescriptor: socket.fd)
+  }
   
   deinit {
     close()
diff --git a/lib/swift/Sources/UnixSocket.swift b/lib/swift/Sources/UnixSocket.swift
new file mode 100644
index 0000000..0b7a637
--- /dev/null
+++ b/lib/swift/Sources/UnixSocket.swift
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+import Darwin
+#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android)
+import Glibc
+import Dispatch
+#endif
+import Foundation
+
+private struct Sys {
+#if os(Linux)
+  static let read = Glibc.read
+  static let write = Glibc.write
+  static let close = Glibc.close
+  static let socket = Glibc.socket
+  static let connect = Glibc.connect
+  static let bind = Glibc.bind
+  static let recv = Glibc.recv
+#else
+  static let read = Darwin.read
+  static let write = Darwin.write
+  static let close = Darwin.close
+  static let socket = Darwin.socket
+  static let connect = Darwin.connect
+  static let bind = Darwin.bind
+  static let recv = Darwin.recv
+#endif
+}
+
+
+public class UnixSocket {
+  public var fd: Int32
+  private var socketAddress: sockaddr_un
+
+  public init(path: String) {
+    socketAddress = sockaddr_un()
+    socketAddress.sun_family = sa_family_t(AF_UNIX)
+
+    let lengthOfPath = path.withCString { Int(strlen($0)) }
+
+    guard lengthOfPath < MemoryLayout.size(ofValue: socketAddress.sun_path) else {
+      fatalError()
+    }
+
+    _ = withUnsafeMutablePointer(to: &socketAddress.sun_path.0) { ptr in
+      path.withCString {
+        strncpy(ptr, $0, lengthOfPath)
+      }
+    }
+
+#if os(Linux)
+    fd = Sys.socket(AF_UNIX, 1 /*SOCK_STREAM*/, 0);
+#else
+    fd = Sys.socket(AF_UNIX, SOCK_STREAM, 0);
+#endif
+
+  }
+  public func connect() -> Int32 {
+    let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
+      $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
+        return $0
+      }
+    }
+    return Sys.connect(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
+  }
+  public func bind() -> Int32 {
+    let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
+      $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
+        return $0
+      }
+    }
+    return Sys.bind(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
+  }
+}