import Foundation import Darwin public enum UnixIPC { public static let defaultSocketPath = ProcessInfo.processInfo.environment["UMN_SOCKET"] ?? FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent("Library/Application Support/UltraMesh/umnd.sock").path public static func connect(path: String = defaultSocketPath) throws -> Int32 { let fd = socket(AF_UNIX, SOCK_STREAM, 0) guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) } do { var address = try unixAddress(path: path) let result = withUnsafePointer(to: &address) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { Darwin.connect(fd, $0, unixAddressLength(path: path)) } } guard result == 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) } return fd } catch { Darwin.close(fd); throw error } } public static func listen(path: String = defaultSocketPath) throws -> Int32 { try FileManager.default.createDirectory(at: URL(fileURLWithPath: path).deletingLastPathComponent(), withIntermediateDirectories: true) unlink(path) let fd = socket(AF_UNIX, SOCK_STREAM, 0) guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) } var address = try unixAddress(path: path) let bound = withUnsafePointer(to: &address) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { Darwin.bind(fd, $0, unixAddressLength(path: path)) } } guard bound == 0, Darwin.listen(fd, 64) == 0 else { let code = errno; Darwin.close(fd); throw POSIXError(.init(rawValue: code) ?? .EIO) } chmod(path, 0o600) return fd } public static func accept(_ server: Int32) throws -> Int32 { let fd = Darwin.accept(server, nil, nil) guard fd >= 0 else { throw POSIXError(.init(rawValue: errno) ?? .EIO) } return fd } public static func send(_ message: IPCMessage, to fd: Int32) throws { try writeAll(FrameCodec.encode(message), to: fd) } public static func receive(from fd: Int32) throws -> IPCMessage? { guard let prefix = try readExact(4, from: fd) else { return nil } let count = try FrameCodec.bodyLength(from: prefix) guard let body = try readExact(count, from: fd) else { throw UMNError.invalidFrame } return try FrameCodec.decode(IPCMessage.self, body: body) } public static func close(_ fd: Int32) { Darwin.shutdown(fd, SHUT_RDWR); Darwin.close(fd) } private static func readExact(_ count: Int, from fd: Int32) throws -> Data? { var output = Data(); output.reserveCapacity(count) var buffer = [UInt8](repeating: 0, count: min(16_384, count)) while output.count < count { let wanted = min(buffer.count, count - output.count) let result = Darwin.read(fd, &buffer, wanted) if result == 0 { return output.isEmpty ? nil : output } if result < 0 { if errno == EINTR { continue } throw POSIXError(.init(rawValue: errno) ?? .EIO) } output.append(buffer, count: result) } return output } private static func writeAll(_ data: Data, to fd: Int32) throws { try data.withUnsafeBytes { raw in guard let base = raw.baseAddress else { return } var offset = 0 while offset < data.count { let result = Darwin.write(fd, base.advanced(by: offset), data.count - offset) if result < 0 { if errno == EINTR { continue } throw POSIXError(.init(rawValue: errno) ?? .EIO) } offset += result } } } private static func unixAddress(path: String) throws -> sockaddr_un { guard path.utf8.count < MemoryLayout.size(ofValue: sockaddr_un().sun_path) else { throw UMNError.message("IPC socket path is too long") } var address = sockaddr_un(); address.sun_family = sa_family_t(AF_UNIX) let capacity = MemoryLayout.size(ofValue: address.sun_path) withUnsafeMutablePointer(to: &address.sun_path) { pointer in pointer.withMemoryRebound(to: CChar.self, capacity: capacity) { destination in path.withCString { source in _ = strncpy(destination, source, capacity - 1) } } } return address } private static func unixAddressLength(path: String) -> socklen_t { socklen_t(MemoryLayout.size + path.utf8.count + 1) } }