THRIFT-4838: Add unix socket support for Swift
Client: Swift
Patch: Kino Roy
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5ff30cf..188bdf8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -624,6 +624,9 @@
             test/rs/bin/* \
             test/go/bin/*
 
+      - name: Create tmp domain socket folder
+        run: mkdir /tmp/v0.16
+
       - name: Run cross test
         env:
           THRIFT_CROSSTEST_CONCURRENCY: 4
diff --git a/LANGUAGES.md b/LANGUAGES.md
index f7108a7..27877ba 100644
--- a/LANGUAGES.md
+++ b/LANGUAGES.md
@@ -350,7 +350,7 @@
 <!-- Build Systems ---------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
 <!-- Language Levels -------><td colspan=2>5.7</td>
 <!-- Field types -----------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
-<!-- Low-Level Transports --><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
+<!-- Low-Level Transports --><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
 <!-- Transport Wrappers ----><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
 <!-- Protocols -------------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
 <!-- Servers ---------------><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
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)))
+  }
+}
diff --git a/test/swift/CrossTests/Sources/TestClient/main.swift b/test/swift/CrossTests/Sources/TestClient/main.swift
index 6817981..d627408 100644
--- a/test/swift/CrossTests/Sources/TestClient/main.swift
+++ b/test/swift/CrossTests/Sources/TestClient/main.swift
@@ -40,7 +40,12 @@
   }
 
   static func getTransport(parameters: TestClientParameters) throws -> TTransport {
-    let socketTransport = try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
+      let socketTransport: TTransport = try { () throws -> TTransport in
+          if let domainSocket = parameters.domainSocket {
+            return try TSocketTransport(path: domainSocket)
+          }
+          return try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
+      }()
     if parameters.transport == .framed {
       return TFramedTransport(transport: socketTransport)
     } 
diff --git a/test/swift/CrossTests/Sources/TestServer/main.swift b/test/swift/CrossTests/Sources/TestServer/main.swift
index 15564d3..433bde7 100644
--- a/test/swift/CrossTests/Sources/TestServer/main.swift
+++ b/test/swift/CrossTests/Sources/TestServer/main.swift
@@ -34,19 +34,31 @@
     let processor = ThriftTestProcessor(service: service)
     
     
-    switch (parameters.proto, parameters.transport) {
-    case (.binary, .buffered):
+    switch (parameters.proto, parameters.transport, parameters.domainSocket) {
+    case (.binary, .buffered, .none):
       let proto = TBinaryProtocol.self
       server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
-    case (.binary, .framed):
+    case (.binary, .framed, .none):
       let proto = TBinaryProtocol.self
       server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
-    case (.compact, .buffered):
+    case (.compact, .buffered, .none):
       let proto = TCompactProtocol.self
       server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
-    case (.compact, .framed):
+    case (.compact, .framed, .none):
       let proto = TCompactProtocol.self
       server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
+    case (.binary, .buffered, .some(let domainSocket)):
+      let proto = TBinaryProtocol.self
+      server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+    case (.binary, .framed, .some(let domainSocket)):
+      let proto = TBinaryProtocol.self
+      server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+    case (.compact, .buffered, .some(let domainSocket)):
+      let proto = TCompactProtocol.self
+      server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+    case (.compact, .framed, .some(let domainSocket)):
+      let proto = TCompactProtocol.self
+      server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
     default:
       throw ParserError.unsupportedOption
     }
diff --git a/test/tests.json b/test/tests.json
index 015a559..c9bd068 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -797,14 +797,14 @@
       "workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
       "protocols": ["binary", "compact"],
       "transports": ["buffered", "framed"],
-      "sockets": ["ip"]
+      "sockets": ["ip", "domain"]
     },
     "client": {
       "command": ["TestClient"],
       "workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
       "protocols": ["binary", "compact"],
       "transports": ["buffered", "framed"],
-      "sockets": ["ip"]
+      "sockets": ["ip", "domain"]
     }
   }
 ]