115 lines
4.7 KiB
Swift
115 lines
4.7 KiB
Swift
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<sa_family_t>.size + path.utf8.count + 1)
|
|
}
|
|
}
|