@braedonsaunders/appkit-desk 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +55 -0
  2. package/backend.d.ts +28 -7
  3. package/backend.d.ts.map +1 -1
  4. package/backend.js +18 -521
  5. package/backend.js.map +1 -1
  6. package/host.d.ts +4 -1
  7. package/host.d.ts.map +1 -1
  8. package/host.js +31 -16
  9. package/host.js.map +1 -1
  10. package/index.d.ts +2 -0
  11. package/index.d.ts.map +1 -1
  12. package/index.js +2 -0
  13. package/index.js.map +1 -1
  14. package/machine.d.ts +145 -0
  15. package/machine.d.ts.map +1 -0
  16. package/machine.js +518 -0
  17. package/machine.js.map +1 -0
  18. package/package.json +2 -2
  19. package/plan.d.ts +129 -31
  20. package/plan.d.ts.map +1 -1
  21. package/plan.js +165 -9
  22. package/plan.js.map +1 -1
  23. package/port-relay.d.ts +13 -0
  24. package/port-relay.d.ts.map +1 -0
  25. package/port-relay.js +56 -0
  26. package/port-relay.js.map +1 -0
  27. package/virtualization.d.ts +16 -0
  28. package/virtualization.d.ts.map +1 -0
  29. package/virtualization.js +148 -0
  30. package/virtualization.js.map +1 -0
  31. package/vz/Package.swift +23 -0
  32. package/vz/Sources/DeskNet/Addresses.swift +106 -0
  33. package/vz/Sources/DeskNet/DHCP.swift +111 -0
  34. package/vz/Sources/DeskNet/Fence.swift +57 -0
  35. package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
  36. package/vz/Sources/DeskNet/Packets.swift +355 -0
  37. package/vz/Sources/DeskNet/Plumbing.swift +312 -0
  38. package/vz/Sources/DeskNet/Sockets.swift +221 -0
  39. package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
  40. package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
  41. package/vz/Sources/appkit-desk-vz/Helper.swift +505 -0
  42. package/vz/Sources/appkit-desk-vz/main.swift +38 -0
  43. package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
  44. package/vz/appkit-desk-vz.entitlements +8 -0
  45. package/vz/build.sh +23 -0
  46. package/vz/check/extract-boot.py +221 -0
@@ -0,0 +1,355 @@
1
+ /// Encoding and decoding of the frames that cross the virtual link: Ethernet,
2
+ /// ARP, IPv4, TCP, UDP and ICMP. Everything here is pure and works on byte
3
+ /// arrays, so it is tested without a guest.
4
+
5
+ public enum EtherType {
6
+ public static let ipv4: UInt16 = 0x0800
7
+ public static let arp: UInt16 = 0x0806
8
+ }
9
+
10
+ public enum IPProtocol {
11
+ public static let icmp: UInt8 = 1
12
+ public static let tcp: UInt8 = 6
13
+ public static let udp: UInt8 = 17
14
+ }
15
+
16
+ @inline(__always) func be16(_ bytes: [UInt8], _ offset: Int) -> UInt16 {
17
+ UInt16(bytes[offset]) << 8 | UInt16(bytes[offset + 1])
18
+ }
19
+
20
+ @inline(__always) func be32(_ bytes: [UInt8], _ offset: Int) -> UInt32 {
21
+ UInt32(bytes[offset]) << 24 | UInt32(bytes[offset + 1]) << 16 | UInt32(bytes[offset + 2]) << 8
22
+ | UInt32(bytes[offset + 3])
23
+ }
24
+
25
+ @inline(__always) func put16(_ bytes: inout [UInt8], _ offset: Int, _ value: UInt16) {
26
+ bytes[offset] = UInt8(value >> 8)
27
+ bytes[offset + 1] = UInt8(value & 0xff)
28
+ }
29
+
30
+ @inline(__always) func put32(_ bytes: inout [UInt8], _ offset: Int, _ value: UInt32) {
31
+ bytes[offset] = UInt8(value >> 24)
32
+ bytes[offset + 1] = UInt8(value >> 16 & 0xff)
33
+ bytes[offset + 2] = UInt8(value >> 8 & 0xff)
34
+ bytes[offset + 3] = UInt8(value & 0xff)
35
+ }
36
+
37
+ /// The Internet checksum (RFC 1071) over `bytes`, folded and complemented.
38
+ public func internetChecksum(_ bytes: [UInt8], initial: UInt32 = 0) -> UInt16 {
39
+ var sum = initial
40
+ var index = 0
41
+ let count = bytes.count
42
+ while index + 1 < count {
43
+ sum &+= UInt32(bytes[index]) << 8 | UInt32(bytes[index + 1])
44
+ index += 2
45
+ }
46
+ if index < count { sum &+= UInt32(bytes[index]) << 8 }
47
+ while sum >> 16 != 0 { sum = (sum & 0xffff) &+ (sum >> 16) }
48
+ return ~UInt16(sum & 0xffff)
49
+ }
50
+
51
+ func pseudoHeaderSum(source: IPv4, destination: IPv4, protocol proto: UInt8, length: Int) -> UInt32 {
52
+ var sum: UInt32 = 0
53
+ sum &+= source.value >> 16
54
+ sum &+= source.value & 0xffff
55
+ sum &+= destination.value >> 16
56
+ sum &+= destination.value & 0xffff
57
+ sum &+= UInt32(proto)
58
+ sum &+= UInt32(length)
59
+ return sum
60
+ }
61
+
62
+ // MARK: - Ethernet
63
+
64
+ public struct EthernetFrame: Sendable {
65
+ public var destination: MAC
66
+ public var source: MAC
67
+ public var type: UInt16
68
+ public var payload: [UInt8]
69
+
70
+ public init(destination: MAC, source: MAC, type: UInt16, payload: [UInt8]) {
71
+ self.destination = destination
72
+ self.source = source
73
+ self.type = type
74
+ self.payload = payload
75
+ }
76
+
77
+ public init?(parsing bytes: [UInt8]) {
78
+ guard bytes.count >= 14 else { return nil }
79
+ destination = MAC(Array(bytes[0..<6]))
80
+ source = MAC(Array(bytes[6..<12]))
81
+ type = be16(bytes, 12)
82
+ payload = Array(bytes[14...])
83
+ }
84
+
85
+ public var encoded: [UInt8] {
86
+ var bytes = destination.bytes + source.bytes + [0, 0]
87
+ put16(&bytes, 12, type)
88
+ return bytes + payload
89
+ }
90
+ }
91
+
92
+ // MARK: - ARP
93
+
94
+ public struct ARPPacket: Sendable, Equatable {
95
+ public static let request: UInt16 = 1
96
+ public static let reply: UInt16 = 2
97
+
98
+ public var operation: UInt16
99
+ public var senderMAC: MAC
100
+ public var senderIP: IPv4
101
+ public var targetMAC: MAC
102
+ public var targetIP: IPv4
103
+
104
+ public init(operation: UInt16, senderMAC: MAC, senderIP: IPv4, targetMAC: MAC, targetIP: IPv4) {
105
+ self.operation = operation
106
+ self.senderMAC = senderMAC
107
+ self.senderIP = senderIP
108
+ self.targetMAC = targetMAC
109
+ self.targetIP = targetIP
110
+ }
111
+
112
+ public init?(parsing bytes: [UInt8]) {
113
+ // Ethernet hardware (1), IPv4 protocol, 6-byte MACs, 4-byte addresses.
114
+ guard bytes.count >= 28, be16(bytes, 0) == 1, be16(bytes, 2) == EtherType.ipv4,
115
+ bytes[4] == 6, bytes[5] == 4
116
+ else { return nil }
117
+ operation = be16(bytes, 6)
118
+ senderMAC = MAC(Array(bytes[8..<14]))
119
+ senderIP = IPv4(bytes: bytes, at: 14)
120
+ targetMAC = MAC(Array(bytes[18..<24]))
121
+ targetIP = IPv4(bytes: bytes, at: 24)
122
+ }
123
+
124
+ public var encoded: [UInt8] {
125
+ var bytes: [UInt8] = [0, 1, 0x08, 0x00, 6, 4, 0, 0]
126
+ put16(&bytes, 6, operation)
127
+ return bytes + senderMAC.bytes + senderIP.bytes + targetMAC.bytes + targetIP.bytes
128
+ }
129
+ }
130
+
131
+ // MARK: - IPv4
132
+
133
+ public struct IPv4Packet: Sendable {
134
+ public var source: IPv4
135
+ public var destination: IPv4
136
+ public var proto: UInt8
137
+ public var ttl: UInt8
138
+ public var identification: UInt16
139
+ /// True when this is any piece of a fragmented datagram.
140
+ public var isFragment: Bool
141
+ /// The header exactly as received, for quoting in ICMP errors.
142
+ public var header: [UInt8]
143
+ public var payload: [UInt8]
144
+
145
+ public init?(parsing bytes: [UInt8]) {
146
+ guard bytes.count >= 20, bytes[0] >> 4 == 4 else { return nil }
147
+ let headerLength = Int(bytes[0] & 0x0f) * 4
148
+ let totalLength = Int(be16(bytes, 2))
149
+ guard headerLength >= 20, totalLength >= headerLength, bytes.count >= totalLength else {
150
+ return nil
151
+ }
152
+ let flagsAndOffset = be16(bytes, 6)
153
+ isFragment = flagsAndOffset & 0x2000 != 0 || flagsAndOffset & 0x1fff != 0
154
+ identification = be16(bytes, 4)
155
+ ttl = bytes[8]
156
+ proto = bytes[9]
157
+ source = IPv4(bytes: bytes, at: 12)
158
+ destination = IPv4(bytes: bytes, at: 16)
159
+ header = Array(bytes[0..<headerLength])
160
+ payload = Array(bytes[headerLength..<totalLength])
161
+ }
162
+
163
+ /// Build a datagram with a correct header checksum and Don't Fragment set.
164
+ public static func encode(
165
+ source: IPv4, destination: IPv4, proto: UInt8, payload: [UInt8], identification: UInt16 = 0,
166
+ ttl: UInt8 = 64
167
+ ) -> [UInt8] {
168
+ var header = [UInt8](repeating: 0, count: 20)
169
+ header[0] = 0x45
170
+ put16(&header, 2, UInt16(20 + payload.count))
171
+ put16(&header, 4, identification)
172
+ put16(&header, 6, 0x4000)
173
+ header[8] = ttl
174
+ header[9] = proto
175
+ put32(&header, 12, source.value)
176
+ put32(&header, 16, destination.value)
177
+ put16(&header, 10, internetChecksum(header))
178
+ return header + payload
179
+ }
180
+ }
181
+
182
+ // MARK: - TCP
183
+
184
+ public struct TCPFlags: OptionSet, Sendable, Hashable {
185
+ public let rawValue: UInt8
186
+ public init(rawValue: UInt8) { self.rawValue = rawValue }
187
+ public static let fin = TCPFlags(rawValue: 0x01)
188
+ public static let syn = TCPFlags(rawValue: 0x02)
189
+ public static let rst = TCPFlags(rawValue: 0x04)
190
+ public static let psh = TCPFlags(rawValue: 0x08)
191
+ public static let ack = TCPFlags(rawValue: 0x10)
192
+ }
193
+
194
+ public struct TCPSegment: Sendable {
195
+ public var sourcePort: UInt16
196
+ public var destinationPort: UInt16
197
+ public var sequence: UInt32
198
+ public var acknowledgement: UInt32
199
+ public var flags: TCPFlags
200
+ public var window: UInt16
201
+ /// The maximum segment size option, when the segment carries one.
202
+ public var mss: UInt16?
203
+ public var payload: [UInt8]
204
+
205
+ public init(
206
+ sourcePort: UInt16, destinationPort: UInt16, sequence: UInt32, acknowledgement: UInt32,
207
+ flags: TCPFlags, window: UInt16, mss: UInt16? = nil, payload: [UInt8] = []
208
+ ) {
209
+ self.sourcePort = sourcePort
210
+ self.destinationPort = destinationPort
211
+ self.sequence = sequence
212
+ self.acknowledgement = acknowledgement
213
+ self.flags = flags
214
+ self.window = window
215
+ self.mss = mss
216
+ self.payload = payload
217
+ }
218
+
219
+ public init?(parsing bytes: [UInt8]) {
220
+ guard bytes.count >= 20 else { return nil }
221
+ let dataOffset = Int(bytes[12] >> 4) * 4
222
+ guard dataOffset >= 20, bytes.count >= dataOffset else { return nil }
223
+ sourcePort = be16(bytes, 0)
224
+ destinationPort = be16(bytes, 2)
225
+ sequence = be32(bytes, 4)
226
+ acknowledgement = be32(bytes, 8)
227
+ flags = TCPFlags(rawValue: bytes[13] & 0x1f)
228
+ window = be16(bytes, 14)
229
+ mss = nil
230
+ var index = 20
231
+ while index < dataOffset {
232
+ let kind = bytes[index]
233
+ if kind == 0 { break }
234
+ if kind == 1 {
235
+ index += 1
236
+ continue
237
+ }
238
+ guard index + 1 < dataOffset else { break }
239
+ let length = Int(bytes[index + 1])
240
+ guard length >= 2, index + length <= dataOffset else { break }
241
+ if kind == 2, length == 4 { mss = be16(bytes, index + 2) }
242
+ index += length
243
+ }
244
+ payload = Array(bytes[dataOffset...])
245
+ }
246
+
247
+ /// The sequence space this segment occupies: its payload plus SYN and FIN.
248
+ public var sequenceLength: UInt32 {
249
+ UInt32(payload.count) + (flags.contains(.syn) ? 1 : 0) + (flags.contains(.fin) ? 1 : 0)
250
+ }
251
+
252
+ public func encoded(source: IPv4, destination: IPv4) -> [UInt8] {
253
+ let optionBytes: [UInt8] = mss.map { [2, 4, UInt8($0 >> 8), UInt8($0 & 0xff)] } ?? []
254
+ let headerLength = 20 + optionBytes.count
255
+ var bytes = [UInt8](repeating: 0, count: 20)
256
+ put16(&bytes, 0, sourcePort)
257
+ put16(&bytes, 2, destinationPort)
258
+ put32(&bytes, 4, sequence)
259
+ put32(&bytes, 8, acknowledgement)
260
+ bytes[12] = UInt8(headerLength / 4) << 4
261
+ bytes[13] = flags.rawValue
262
+ put16(&bytes, 14, window)
263
+ bytes += optionBytes
264
+ bytes += payload
265
+ let sum = pseudoHeaderSum(
266
+ source: source, destination: destination, protocol: IPProtocol.tcp, length: bytes.count)
267
+ put16(&bytes, 16, internetChecksum(bytes, initial: sum))
268
+ return bytes
269
+ }
270
+ }
271
+
272
+ // MARK: - UDP
273
+
274
+ public struct UDPDatagram: Sendable {
275
+ public var sourcePort: UInt16
276
+ public var destinationPort: UInt16
277
+ public var payload: [UInt8]
278
+
279
+ public init(sourcePort: UInt16, destinationPort: UInt16, payload: [UInt8]) {
280
+ self.sourcePort = sourcePort
281
+ self.destinationPort = destinationPort
282
+ self.payload = payload
283
+ }
284
+
285
+ public init?(parsing bytes: [UInt8]) {
286
+ guard bytes.count >= 8 else { return nil }
287
+ let length = Int(be16(bytes, 4))
288
+ guard length >= 8, length <= bytes.count else { return nil }
289
+ sourcePort = be16(bytes, 0)
290
+ destinationPort = be16(bytes, 2)
291
+ payload = Array(bytes[8..<length])
292
+ }
293
+
294
+ public func encoded(source: IPv4, destination: IPv4) -> [UInt8] {
295
+ var bytes = [UInt8](repeating: 0, count: 8)
296
+ put16(&bytes, 0, sourcePort)
297
+ put16(&bytes, 2, destinationPort)
298
+ put16(&bytes, 4, UInt16(8 + payload.count))
299
+ bytes += payload
300
+ let sum = pseudoHeaderSum(
301
+ source: source, destination: destination, protocol: IPProtocol.udp, length: bytes.count)
302
+ var checksum = internetChecksum(bytes, initial: sum)
303
+ // A computed zero is sent as all ones; zero means "no checksum" in UDP.
304
+ if checksum == 0 { checksum = 0xffff }
305
+ put16(&bytes, 6, checksum)
306
+ return bytes
307
+ }
308
+ }
309
+
310
+ // MARK: - ICMP
311
+
312
+ public struct ICMPMessage: Sendable {
313
+ public static let echoReply: UInt8 = 0
314
+ public static let destinationUnreachable: UInt8 = 3
315
+ public static let echoRequest: UInt8 = 8
316
+
317
+ /// Destination-unreachable codes the stack sends.
318
+ public static let hostUnreachable: UInt8 = 1
319
+ public static let portUnreachable: UInt8 = 3
320
+ public static let administrativelyProhibited: UInt8 = 13
321
+
322
+ public var type: UInt8
323
+ public var code: UInt8
324
+ /// The four bytes after the checksum: identifier and sequence for echo.
325
+ public var rest: UInt32
326
+ public var payload: [UInt8]
327
+
328
+ public init(type: UInt8, code: UInt8, rest: UInt32, payload: [UInt8]) {
329
+ self.type = type
330
+ self.code = code
331
+ self.rest = rest
332
+ self.payload = payload
333
+ }
334
+
335
+ public init?(parsing bytes: [UInt8]) {
336
+ guard bytes.count >= 8 else { return nil }
337
+ type = bytes[0]
338
+ code = bytes[1]
339
+ rest = be32(bytes, 4)
340
+ payload = Array(bytes[8...])
341
+ }
342
+
343
+ public var identifier: UInt16 { UInt16(rest >> 16) }
344
+ public var sequenceNumber: UInt16 { UInt16(rest & 0xffff) }
345
+
346
+ public var encoded: [UInt8] {
347
+ var bytes = [UInt8](repeating: 0, count: 8)
348
+ bytes[0] = type
349
+ bytes[1] = code
350
+ put32(&bytes, 4, rest)
351
+ bytes += payload
352
+ put16(&bytes, 2, internetChecksum(bytes))
353
+ return bytes
354
+ }
355
+ }
@@ -0,0 +1,312 @@
1
+ import Darwin
2
+ import Dispatch
3
+
4
+ /// The helper's end of the datagram socket pair behind the guest NIC: one
5
+ /// datagram is one Ethernet frame.
6
+ @MainActor
7
+ public final class DatagramLink {
8
+ public var onFrame: (([UInt8]) -> Void)?
9
+ private let socket: FDWatcher
10
+
11
+ /// The largest frame the link carries, with headroom for a guest that
12
+ /// offloads segmentation.
13
+ static let maximumFrame = 65_550
14
+
15
+ public init(fd: Int32) {
16
+ Socket.setNonBlocking(fd)
17
+ socket = FDWatcher(fd: fd)
18
+ socket.onReadable = { [weak self] in self?.readable() }
19
+ socket.wantRead(true)
20
+ }
21
+
22
+ /// Make a connected pair sized for bursts. Apple recommends the receiving
23
+ /// buffer be at least twice the sending one. Returns (helper end, VM end).
24
+ public static func makePair() throws -> (Int32, Int32) {
25
+ var fds: [Int32] = [0, 0]
26
+ guard socketpair(AF_UNIX, SOCK_DGRAM, 0, &fds) == 0 else { throw POSIXFailure("socketpair") }
27
+ for fd in fds {
28
+ Socket.setInt(fd, SOL_SOCKET, SO_SNDBUF, 1 << 20)
29
+ Socket.setInt(fd, SOL_SOCKET, SO_RCVBUF, 4 << 20)
30
+ }
31
+ return (fds[0], fds[1])
32
+ }
33
+
34
+ /// Send one frame. A full buffer drops it, as a wire would; TCP recovers.
35
+ public func send(_ frame: [UInt8]) {
36
+ guard !socket.isClosed else { return }
37
+ _ = frame.withUnsafeBytes { Darwin.send(socket.fd, $0.baseAddress, frame.count, 0) }
38
+ }
39
+
40
+ private func readable() {
41
+ var buffer = [UInt8](repeating: 0, count: Self.maximumFrame)
42
+ while true {
43
+ let count = buffer.withUnsafeMutableBytes { Darwin.recv(socket.fd, $0.baseAddress, $0.count, 0) }
44
+ if count <= 0 { return }
45
+ onFrame?(Array(buffer[0..<count]))
46
+ if socket.isClosed { return }
47
+ }
48
+ }
49
+
50
+ public func close() { socket.close() }
51
+ }
52
+
53
+ /// A Unix-socket listener speaking the Cloud Hypervisor hybrid-vsock preamble.
54
+ ///
55
+ /// A client connects and writes `CONNECT <port>\n`. The listener hands the
56
+ /// connection and the port to `onRequest`, which either completes it — writes
57
+ /// `OK <number>\n` and carries the stream — or closes it with nothing written,
58
+ /// which is how a refused port presents. Anything else on the first line, or
59
+ /// no line within the deadline, closes the connection.
60
+ @MainActor
61
+ public final class HandshakeListener {
62
+ public typealias Request = (_ client: Int32, _ port: UInt32, _ early: [UInt8]) -> Void
63
+
64
+ public let path: String
65
+ private let listener: FDWatcher
66
+ private let onRequest: Request
67
+ private var pending: [ObjectIdentifier: PendingHandshake] = [:]
68
+
69
+ static let lineLimit = 128
70
+ static let deadlineSeconds = 10
71
+
72
+ public init(path: String, onRequest: @escaping Request) throws {
73
+ self.path = path
74
+ self.onRequest = onRequest
75
+ var address = sockaddr_un()
76
+ address.sun_family = sa_family_t(AF_UNIX)
77
+ let capacity = MemoryLayout.size(ofValue: address.sun_path)
78
+ let pathBytes = Array(path.utf8)
79
+ guard pathBytes.count < capacity else {
80
+ throw POSIXFailure("socket path \(path) is longer than \(capacity - 1) bytes", ENAMETOOLONG)
81
+ }
82
+ withUnsafeMutableBytes(of: &address.sun_path) { raw in
83
+ raw.copyBytes(from: pathBytes)
84
+ raw[pathBytes.count] = 0
85
+ }
86
+ address.sun_len = UInt8(MemoryLayout<sockaddr_un>.size)
87
+ let fd = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
88
+ guard fd >= 0 else { throw POSIXFailure("socket") }
89
+ unlink(path)
90
+ let bound = withUnsafePointer(to: &address) {
91
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
92
+ Darwin.bind(fd, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
93
+ }
94
+ }
95
+ guard bound == 0 else {
96
+ let failure = POSIXFailure("bind \(path)")
97
+ Darwin.close(fd)
98
+ throw failure
99
+ }
100
+ // Only this user may drive the guest through the socket.
101
+ chmod(path, 0o600)
102
+ guard Darwin.listen(fd, 64) == 0 else {
103
+ let failure = POSIXFailure("listen \(path)")
104
+ Darwin.close(fd)
105
+ throw failure
106
+ }
107
+ Socket.setNonBlocking(fd)
108
+ listener = FDWatcher(fd: fd)
109
+ listener.onReadable = { [weak self] in self?.accept() }
110
+ listener.wantRead(true)
111
+ }
112
+
113
+ private func accept() {
114
+ while true {
115
+ let client = Darwin.accept(listener.fd, nil, nil)
116
+ if client < 0 { return }
117
+ Socket.setNonBlocking(client)
118
+ Socket.setNoSigPipe(client)
119
+ let handshake = PendingHandshake(fd: client)
120
+ let id = ObjectIdentifier(handshake)
121
+ pending[id] = handshake
122
+ handshake.onComplete = { [weak self] port, early in
123
+ guard let self else { return }
124
+ self.pending.removeValue(forKey: id)
125
+ if let port, let early {
126
+ // Hand over a duplicate; the watcher closes the original.
127
+ let owned = dup(client)
128
+ handshake.finish()
129
+ if owned >= 0 { self.onRequest(owned, port, early) }
130
+ } else {
131
+ handshake.finish()
132
+ }
133
+ }
134
+ handshake.start()
135
+ }
136
+ }
137
+
138
+ public func close() {
139
+ listener.close()
140
+ for handshake in pending.values { handshake.finish() }
141
+ pending.removeAll()
142
+ unlink(path)
143
+ }
144
+ }
145
+
146
+ @MainActor
147
+ final class PendingHandshake {
148
+ var onComplete: ((UInt32?, [UInt8]?) -> Void)?
149
+ private let socket: FDWatcher
150
+ private var buffer: [UInt8] = []
151
+ private var timer: DispatchSourceTimer?
152
+
153
+ init(fd: Int32) {
154
+ socket = FDWatcher(fd: fd)
155
+ }
156
+
157
+ func start() {
158
+ socket.onReadable = { [weak self] in self?.readable() }
159
+ socket.wantRead(true)
160
+ let timer = DispatchSource.makeTimerSource(queue: .main)
161
+ timer.schedule(deadline: .now() + .seconds(HandshakeListener.deadlineSeconds))
162
+ timer.setEventHandler { @Sendable [weak self] in Task { @MainActor in self?.complete(nil, nil) } }
163
+ timer.resume()
164
+ self.timer = timer
165
+ }
166
+
167
+ private func readable() {
168
+ let bytes: [UInt8]?
169
+ do {
170
+ bytes = try Socket.readAvailable(socket.fd, limit: HandshakeListener.lineLimit)
171
+ } catch {
172
+ return complete(nil, nil)
173
+ }
174
+ guard let bytes else { return }
175
+ if bytes.isEmpty { return complete(nil, nil) }
176
+ buffer += bytes
177
+ guard let newline = buffer.firstIndex(of: 0x0a) else {
178
+ if buffer.count > HandshakeListener.lineLimit { complete(nil, nil) }
179
+ return
180
+ }
181
+ let line = String(decoding: buffer[0..<newline], as: UTF8.self)
182
+ let early = Array(buffer[(newline + 1)...])
183
+ complete(Self.parse(line), early)
184
+ }
185
+
186
+ /// `CONNECT <port>`, exactly. A trailing carriage return is tolerated.
187
+ nonisolated static func parse(_ line: String) -> UInt32? {
188
+ var text = Substring(line)
189
+ if text.hasSuffix("\r") { text = text.dropLast() }
190
+ let parts = text.split(separator: " ", omittingEmptySubsequences: false)
191
+ guard parts.count == 2, parts[0] == "CONNECT", !parts[1].isEmpty,
192
+ parts[1].allSatisfy({ $0.isASCII && $0.isNumber })
193
+ else { return nil }
194
+ return UInt32(parts[1])
195
+ }
196
+
197
+ private func complete(_ port: UInt32?, _ early: [UInt8]?) {
198
+ guard let onComplete else { return }
199
+ self.onComplete = nil
200
+ socket.wantRead(false)
201
+ onComplete(port, port == nil ? nil : early)
202
+ }
203
+
204
+ func finish() {
205
+ timer?.cancel()
206
+ timer = nil
207
+ onComplete = nil
208
+ socket.close()
209
+ }
210
+ }
211
+
212
+ /// Two descriptors joined into one stream, with bounded buffering and
213
+ /// half-close carried across in both directions.
214
+ @MainActor
215
+ public final class Relay {
216
+ private let left: FDWatcher
217
+ private let right: FDWatcher
218
+ private var leftToRight: [UInt8]
219
+ private var rightToLeft: [UInt8] = []
220
+ private var leftEOF = false
221
+ private var rightEOF = false
222
+ private var leftShut = false
223
+ private var rightShut = false
224
+ private var closed = false
225
+ private let onClose: () -> Void
226
+
227
+ static let capacity = 256 * 1024
228
+
229
+ /// `early` is data already read from `left` that must reach `right` first.
230
+ public init(left: Int32, right: Int32, early: [UInt8] = [], onClose: @escaping () -> Void = {}) {
231
+ for fd in [left, right] {
232
+ Socket.setNonBlocking(fd)
233
+ Socket.setNoSigPipe(fd)
234
+ }
235
+ self.left = FDWatcher(fd: left)
236
+ self.right = FDWatcher(fd: right)
237
+ leftToRight = early
238
+ self.onClose = onClose
239
+ self.left.onReadable = { [weak self] in self?.read(fromLeft: true) }
240
+ self.right.onReadable = { [weak self] in self?.read(fromLeft: false) }
241
+ self.left.onWritable = { [weak self] in self?.flush(toRight: false) }
242
+ self.right.onWritable = { [weak self] in self?.flush(toRight: true) }
243
+ flush(toRight: true)
244
+ updateInterest()
245
+ }
246
+
247
+ private func read(fromLeft: Bool) {
248
+ let source = fromLeft ? left : right
249
+ let buffered = fromLeft ? leftToRight.count : rightToLeft.count
250
+ let room = Relay.capacity - buffered
251
+ guard room > 0 else { return updateInterest() }
252
+ let bytes: [UInt8]?
253
+ do {
254
+ bytes = try Socket.readAvailable(source.fd, limit: min(room, 64 * 1024))
255
+ } catch {
256
+ return close()
257
+ }
258
+ guard let bytes else { return }
259
+ if bytes.isEmpty {
260
+ if fromLeft { leftEOF = true } else { rightEOF = true }
261
+ } else if fromLeft {
262
+ leftToRight += bytes
263
+ } else {
264
+ rightToLeft += bytes
265
+ }
266
+ flush(toRight: fromLeft)
267
+ updateInterest()
268
+ }
269
+
270
+ private func flush(toRight: Bool) {
271
+ guard !closed else { return }
272
+ let target = toRight ? right : left
273
+ var buffer = toRight ? leftToRight : rightToLeft
274
+ while !buffer.isEmpty {
275
+ let written: Int
276
+ do {
277
+ written = try Socket.writeSome(target.fd, buffer)
278
+ } catch {
279
+ return close()
280
+ }
281
+ if written == 0 { break }
282
+ buffer.removeFirst(written)
283
+ }
284
+ if toRight { leftToRight = buffer } else { rightToLeft = buffer }
285
+ target.wantWrite(!buffer.isEmpty)
286
+ if buffer.isEmpty {
287
+ if toRight && leftEOF && !rightShut {
288
+ rightShut = true
289
+ _ = Darwin.shutdown(right.fd, SHUT_WR)
290
+ } else if !toRight && rightEOF && !leftShut {
291
+ leftShut = true
292
+ _ = Darwin.shutdown(left.fd, SHUT_WR)
293
+ }
294
+ }
295
+ if leftShut && rightShut { close() }
296
+ updateInterest()
297
+ }
298
+
299
+ private func updateInterest() {
300
+ guard !closed else { return }
301
+ left.wantRead(!leftEOF && leftToRight.count < Relay.capacity)
302
+ right.wantRead(!rightEOF && rightToLeft.count < Relay.capacity)
303
+ }
304
+
305
+ public func close() {
306
+ guard !closed else { return }
307
+ closed = true
308
+ left.close()
309
+ right.close()
310
+ onClose()
311
+ }
312
+ }