Format the tunnel network, hierarchy and patcher sources

swiftformat output only: wrapped single-line bodies, sorted imports, dropped
redundant self, throws and async, and plain numeric literals. No behavior
change; VPhoneCoreKitTests and the touched FirmwarePatcherTests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lakr
2026-10-01 18:52:12 +09:00
co-authored by Claude Opus 5.5
parent 67d0734a6f
commit 602140a1d3
16 changed files with 256 additions and 111 deletions
+2 -2
View File
@@ -4,8 +4,8 @@ import IcliKit
import VphonedNative
extension GuestAPI {
// AX enable/restore is process-wide. Interface operations share this lock,
// so a flat query cannot race this opt-in native snapshot's restoration.
/// AX enable/restore is process-wide. Interface operations share this lock,
/// so a flat query cannot race this opt-in native snapshot's restoration.
static let interfaceLock = NSRecursiveLock()
private static func hierarchyInteger(
+3 -1
View File
@@ -77,7 +77,9 @@ extension GuestAPI {
throw GuestAPIError.invalidRequest("nested must be a boolean")
}
}
if bool(params, "nested") { return try nestedHierarchy(params) }
if bool(params, "nested") {
return try nestedHierarchy(params)
}
return try uiElements(
maxElements: (params["max_elements"] as? NSNumber)?.intValue ?? 500,
visibleOnly: bool(params, "visible_only", default: true),
@@ -133,7 +133,9 @@ public enum DyldSharedCacheMISTrustAuthPatcher {
static let baseError: UInt32 = 0xE800_8001
/// The high half both seeds share, and the only part that is actually stable.
static var errorHighHalf: Int64 { Int64((seededError >> 16) & 0xFFFF) }
static var errorHighHalf: Int64 {
Int64((seededError >> 16) & 0xFFFF)
}
/// How far back from the string reference the function start may sit. The
/// reference is in the last third of the function; 1024 instructions is
@@ -69,7 +69,9 @@ extension FirmwarePipeline {
func pristineInput(for fileURL: URL) throws -> (url: URL, created: Bool) {
guard let stashURL = originalURL(for: fileURL) else { return (fileURL, false) }
let fm = FileManager.default
if fm.fileExists(atPath: stashURL.path) { return (stashURL, false) }
if fm.fileExists(atPath: stashURL.path) {
return (stashURL, false)
}
try fm.createDirectory(at: stashURL.deletingLastPathComponent(), withIntermediateDirectories: true)
try fm.copyItem(at: fileURL, to: stashURL)
return (stashURL, true)
@@ -88,7 +90,9 @@ extension FirmwarePipeline {
guard let stashURL = originalURL(for: fileURL),
fm.fileExists(atPath: stashURL.path)
else { return false }
if try filesMatch(stashURL, fileURL) { return false }
if try filesMatch(stashURL, fileURL) {
return false
}
if fm.fileExists(atPath: fileURL.path) {
try fm.removeItem(at: fileURL)
}
@@ -598,6 +598,7 @@ struct DyldSharedCacheMISTrustAuthShapeDetectorTests {
}
// MARK: - The 24A435 shape: a seeded base plus a derivation
//
// Measured on a pristine `iPhone17,3_27.0_24A435` SystemOS cryptex, decrypted
// and mounted read-only. `libmis` there does not materialise `0xE8008026`
@@ -29,7 +29,9 @@ private final class OnceOnlyBytePatcher: Patcher {
let verbose = false
let data: Data
init(data: Data) { self.data = data }
init(data: Data) {
self.data = data
}
func findAll() throws -> [PatchRecord] {
guard data.first == 0x00 else { return [] }
@@ -45,14 +47,21 @@ private final class OnceOnlyBytePatcher: Patcher {
]
}
func apply() throws -> Int { 1 }
func apply() throws -> Int {
1
}
}
/// Bytes in, bytes out. The shipped loader repackages IM4P containers, which these
/// synthetic files are not.
private struct RawFirmwareLoader: FirmwarePipeline.FirmwareLoader {
func load(from url: URL) throws -> Data { try Data(contentsOf: url) }
func save(_ data: Data, to url: URL) throws { try data.write(to: url) }
func load(from url: URL) throws -> Data {
try Data(contentsOf: url)
}
func save(_ data: Data, to url: URL) throws {
try data.write(to: url)
}
}
/// A VM directory holding one restore tree with one patchable file in it.
@@ -72,9 +81,13 @@ private struct FakeVM {
try Data([0x00, 0x11, 0x22]).write(to: componentURL)
}
func remove() { try? FileManager.default.removeItem(at: root) }
func remove() {
try? FileManager.default.removeItem(at: root)
}
var componentBytes: Data { (try? Data(contentsOf: componentURL)) ?? Data() }
var componentBytes: Data {
(try? Data(contentsOf: componentURL)) ?? Data()
}
var stashURL: URL {
root.appendingPathComponent(FirmwarePipeline.originalsDirectoryName)
@@ -209,7 +222,7 @@ struct FirmwarePipelineOriginalsTests {
#expect(pipeline.originalURL(for: vm.stashURL) == nil)
}
@Test func `only the two whole-tree components opt out of keeping an original`() throws {
@Test func `only the two whole-tree components opt out of keeping an original`() {
let root = URL(fileURLWithPath: NSTemporaryDirectory())
let pipeline = FirmwarePipeline(vmDirectory: root, variant: .less, verbose: false)
let components = pipeline.buildComponentList(restoreDir: root, iOSBase: VPhoneVersion("26.6.2"))
@@ -144,7 +144,9 @@ class VPhoneKeychainBrowserModel {
let item: VPhoneKeychainItem
var value: String
var id: VPhoneKeychainItem.ID { item.id }
var id: VPhoneKeychainItem.ID {
item.id
}
}
// MARK: - Actions
@@ -7,7 +7,9 @@ import Foundation
public struct VPhoneIPv4Address: Sendable, Equatable, Hashable, CustomStringConvertible {
public var raw: UInt32
public init(_ raw: UInt32) { self.raw = raw }
public init(_ raw: UInt32) {
self.raw = raw
}
public init(_ a: UInt8, _ b: UInt8, _ c: UInt8, _ d: UInt8) {
raw = UInt32(a) << 24 | UInt32(b) << 16 | UInt32(c) << 8 | UInt32(d)
@@ -18,7 +20,9 @@ public struct VPhoneIPv4Address: Sendable, Equatable, Hashable, CustomStringConv
UInt8(truncatingIfNeeded: raw >> 8), UInt8(truncatingIfNeeded: raw)]
}
public var description: String { bytes.map(String.init).joined(separator: ".") }
public var description: String {
bytes.map(String.init).joined(separator: ".")
}
/// `255.255.255.255`
public static let broadcast = VPhoneIPv4Address(255, 255, 255, 255)
@@ -72,13 +76,17 @@ public struct VPhoneUserspaceNetworkConfiguration: Sendable, Equatable {
public struct VPhoneMACAddress: Sendable, Equatable, Hashable {
public var bytes: [UInt8]
public init(_ bytes: [UInt8]) { self.bytes = bytes }
public init(_ bytes: [UInt8]) {
self.bytes = bytes
}
/// The gateway's address. Locally administered, unicast, and unlikely to
/// collide with anything.
public static let gateway = VPhoneMACAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])
var hexString: String { bytes.map { String(format: "%02x", $0) }.joined(separator: ":") }
var hexString: String {
bytes.map { String(format: "%02x", $0) }.joined(separator: ":")
}
}
// MARK: - Checksums
@@ -92,8 +100,12 @@ enum VPhoneInternetChecksum {
sum += UInt32(bytes[index]) << 8 | UInt32(bytes[index + 1])
index += 2
}
if index < bytes.count { sum += UInt32(bytes[index]) << 8 }
while sum >> 16 != 0 { sum = (sum & 0xFFFF) + (sum >> 16) }
if index < bytes.count {
sum += UInt32(bytes[index]) << 8
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16)
}
return UInt16(~sum & 0xFFFF)
}
}
@@ -204,12 +216,16 @@ struct VPhoneIPv4Packet {
return fragments
}
var totalLength: Int { 20 + payload.count }
var totalLength: Int {
20 + payload.count
}
/// True when this is one piece of a larger datagram. Nothing here reassembles,
/// so such a packet cannot be handled and has to be dropped rather than
/// misread: only the first fragment even carries the transport header.
var isFragment: Bool { moreFragments || fragmentOffset != 0 }
var isFragment: Bool {
moreFragments || fragmentOffset != 0
}
/// Bit 0x2000 marks a fragment that is not the last; bits 0..12 hold the
/// offset in eight-byte units. A single unfragmented packet leaves both zero.
@@ -222,7 +238,7 @@ struct VPhoneIPv4Packet {
0x45, 0x00,
UInt8(truncatingIfNeeded: totalLength >> 8), UInt8(truncatingIfNeeded: totalLength),
UInt8(truncatingIfNeeded: identification >> 8), UInt8(truncatingIfNeeded: identification),
UInt8((flagsAndFragmentOffset) >> 8), UInt8(truncatingIfNeeded: flagsAndFragmentOffset),
UInt8(flagsAndFragmentOffset >> 8), UInt8(truncatingIfNeeded: flagsAndFragmentOffset),
ttl, proto,
]
header += [0, 0] // checksum placeholder
@@ -387,10 +403,21 @@ struct VPhoneTCPSegment {
self.advertisedWindowScale = advertisedWindowScale
}
var hasSYN: Bool { flags & VPhoneTCPFlags.syn != 0 }
var hasACK: Bool { flags & VPhoneTCPFlags.ack != 0 }
var hasFIN: Bool { flags & VPhoneTCPFlags.fin != 0 }
var hasRST: Bool { flags & VPhoneTCPFlags.rst != 0 }
var hasSYN: Bool {
flags & VPhoneTCPFlags.syn != 0
}
var hasACK: Bool {
flags & VPhoneTCPFlags.ack != 0
}
var hasFIN: Bool {
flags & VPhoneTCPFlags.fin != 0
}
var hasRST: Bool {
flags & VPhoneTCPFlags.rst != 0
}
/// Sequence space this segment occupies. A SYN or FIN each cost one, which
/// matters when acknowledging them.
@@ -408,7 +435,9 @@ struct VPhoneTCPSegment {
}
if let advertisedWindowScale {
options += [3, 3, UInt8(truncatingIfNeeded: advertisedWindowScale)]
while options.count % 4 != 0 { options.append(1) } // NOP padding
while options.count % 4 != 0 {
options.append(1)
} // NOP padding
}
let headerLength = 20 + options.count
var header: [UInt8] = [
@@ -465,8 +494,12 @@ struct VPhoneTCPSegment {
var index = 0
while index < options.count {
let kind = options[index]
if kind == 0 { break } // end of options
if kind == 1 { index += 1; continue } // no-op padding
if kind == 0 {
break
} // end of options
if kind == 1 {
index += 1; continue
} // no-op padding
guard index + 1 < options.count else { break }
let length = Int(options[index + 1])
guard length >= 2, index + length <= options.count else { break }
@@ -139,6 +139,7 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
var windowScaleNegotiated = false
// MARK: Sending toward the guest
//
// The link to the guest is local and does not lose packets, so the only
// way data can go missing is by sending more than the guest has buffer
@@ -159,6 +160,7 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
var unacknowledgedBytes: Int {
Int(Int32(bitPattern: localSequence &- sendUna))
}
/// When the guest's acknowledgment last moved, so a stalled connection
/// can be told from a slow one.
var lastAckAdvance = Date()
@@ -182,6 +184,7 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
var readSuspended = false
// MARK: Sending toward the host
//
// The mirror of the problem above: the host's send buffer can fill too,
// when the guest uploads faster than the server accepts. Waiting for
@@ -210,8 +213,8 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
self.remoteSequence = remoteSequence
self.peerMSS = peerMSS
self.peerWindowScale = peerWindowScale
self.sendWindowRight = localSequence
self.sendUna = localSequence
sendWindowRight = localSequence
sendUna = localSequence
}
}
@@ -222,7 +225,7 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
private var isStopped = false
/// Source of initial sequence numbers. Only has to be unpredictable enough
/// that two connections to the same peer do not look alike.
private var sequenceCounter: UInt32 = UInt32.random(in: 0 ... UInt32.max)
private var sequenceCounter: UInt32 = .random(in: 0 ... UInt32.max)
/// Marks `queue` as ours, so `connectionCount` can tell whether it is already
/// on it rather than deadlocking against itself.
@@ -240,7 +243,9 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
/// queue it reads directly. Hopping unconditionally is what deadlocked
/// `start()` in the UDP forwarder.
var connectionCount: Int {
if DispatchQueue.getSpecific(key: Self.queueKey) != nil { return connections.count }
if DispatchQueue.getSpecific(key: Self.queueKey) != nil {
return connections.count
}
return queue.sync { connections.count }
}
@@ -260,7 +265,9 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
isStopped = true
reaper?.cancel()
reaper = nil
for connection in connections.values { close(connection) }
for connection in connections.values {
close(connection)
}
connections.removeAll()
}
@@ -514,7 +521,9 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
windowSize: advertisedWindowField(connection),
), connection: connection)
finishIfBothClosed(connection)
if connection.state == .closing, connection.pendingToGuest.isEmpty { return }
if connection.state == .closing, connection.pendingToGuest.isEmpty {
return
}
}
// Room again: resume a source that was paused for backpressure.
@@ -622,7 +631,9 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
connection.lastActivity = Date()
connection.pendingToGuest += buffer[0 ..< received]
flushToGuest(connection)
if connection.pendingToGuest.count >= Self.maxPendingToGuest { break }
if connection.pendingToGuest.count >= Self.maxPendingToGuest {
break
}
continue
}
if received == 0 {
@@ -675,12 +686,16 @@ final class VPhoneTCPForwarder: @unchecked Sendable {
private func send(_ segment: VPhoneTCPSegment, connection: Connection) {
if !segment.payload.isEmpty {
if connection.sentNotAcked.isEmpty { connection.sentNotAckedSequence = segment.sequenceNumber }
if connection.sentNotAcked.isEmpty {
connection.sentNotAckedSequence = segment.sequenceNumber
}
connection.sentNotAcked += segment.payload
connection.localSequence &+= UInt32(segment.payload.count)
armRetransmission(connection)
}
if segment.hasSYN || segment.hasFIN { connection.localSequence &+= 1 }
if segment.hasSYN || segment.hasFIN {
connection.localSequence &+= 1
}
deliver(connection.flow, segment)
}
@@ -1,6 +1,6 @@
import Foundation
import SystemConfiguration
import os
import SystemConfiguration
// MARK: - Host resolver lookup
@@ -113,7 +113,9 @@ final class VPhoneUDPForwarder: @unchecked Sendable {
/// queue it reads directly. Hopping unconditionally would deadlock, which is
/// exactly the bug `start()` had.
var sessionCount: Int {
if DispatchQueue.getSpecific(key: Self.queueKey) != nil { return sessions.count }
if DispatchQueue.getSpecific(key: Self.queueKey) != nil {
return sessions.count
}
return queue.sync { sessions.count }
}
@@ -133,7 +135,9 @@ final class VPhoneUDPForwarder: @unchecked Sendable {
isStopped = true
reaper?.cancel()
reaper = nil
for session in sessions.values { closeSession(session) }
for session in sessions.values {
closeSession(session)
}
sessions.removeAll()
}
@@ -242,7 +246,9 @@ final class VPhoneUDPForwarder: @unchecked Sendable {
let received = readBuffer.withUnsafeMutableBytes { raw in
recv(session.socket, raw.baseAddress, raw.count, 0)
}
if received <= 0 { return } // EAGAIN, or an ICMP error on the flow
if received <= 0 {
return
} // EAGAIN, or an ICMP error on the flow
session.lastActivity = Date()
deliver(session.flow, Array(readBuffer[0 ..< received]))
}
@@ -1,6 +1,6 @@
import Foundation
import Virtualization
import os
import Virtualization
// MARK: - Errors
@@ -21,7 +21,9 @@ extension VPhoneUserspaceNetworkError: CustomStringConvertible, LocalizedError {
}
}
public var errorDescription: String? { description }
public var errorDescription: String? {
description
}
}
// MARK: - The device
@@ -130,7 +132,9 @@ public final class VPhoneUserspaceNetwork: @unchecked Sendable {
}
/// The object to hand to `VZVirtioNetworkDeviceConfiguration.attachment`.
public var networkAttachment: VZNetworkDeviceAttachment { attachment }
public var networkAttachment: VZNetworkDeviceAttachment {
attachment
}
/// Begin draining the guest's frames. Idempotent, and a no-op after `stop()`.
public func start() {
@@ -178,7 +182,9 @@ public final class VPhoneUserspaceNetwork: @unchecked Sendable {
let received = frameBuffer.withUnsafeMutableBytes { raw in
recv(socket, raw.baseAddress, raw.count, 0)
}
if received <= 0 { return } // EAGAIN once the queue is empty
if received <= 0 {
return
} // EAGAIN once the queue is empty
let frame = Array(frameBuffer[0 ..< received])
switch responder.handle(frame) {
case .drop:
@@ -266,5 +272,4 @@ public final class VPhoneUserspaceNetwork: @unchecked Sendable {
write(frame)
}
}
}
@@ -26,7 +26,8 @@ struct VPhoneARPMessage {
}
init(operation: UInt16, senderHardware: VPhoneMACAddress, senderProtocol: VPhoneIPv4Address,
targetHardware: VPhoneMACAddress, targetProtocol: VPhoneIPv4Address) {
targetHardware: VPhoneMACAddress, targetProtocol: VPhoneIPv4Address)
{
self.operation = operation
self.senderHardware = senderHardware
self.senderProtocol = senderProtocol
@@ -83,18 +84,23 @@ struct VPhoneDHCPMessage {
var index = 0
while index < options.count {
let code = options[index]
if code == 255 { return nil }
if code == 255 {
return nil
}
guard index + 1 < options.count else { return nil }
let length = Int(options[index + 1])
guard index + 2 + length <= options.count else { return nil }
if code == 53, length == 1 { return MessageType(rawValue: options[index + 2]) }
if code == 53, length == 1 {
return MessageType(rawValue: options[index + 2])
}
index += 2 + length
}
return nil
}
init(operation: UInt8, transactionID: UInt32, clientHardware: VPhoneMACAddress,
broadcastFlag: Bool, options: [UInt8]) {
broadcastFlag: Bool, options: [UInt8])
{
self.operation = operation
self.transactionID = transactionID
self.clientHardware = clientHardware
@@ -119,7 +125,7 @@ struct VPhoneDHCPMessage {
server: VPhoneIPv4Address,
netmask: VPhoneIPv4Address,
mtu: Int,
leaseSeconds: UInt32 = 86_400,
leaseSeconds: UInt32 = 86400,
) -> [UInt8] {
var out = [UInt8](repeating: 0, count: 236)
out[0] = 2 // BOOTREPLY
@@ -142,15 +148,17 @@ struct VPhoneDHCPMessage {
options += [code, UInt8(values.count)] + values
}
append(53, [type.rawValue])
append(54, server.bytes) // server identifier
append(54, server.bytes) // server identifier
append(51, withUnsafeBytes(of: leaseSeconds.bigEndian, Array.init)) // lease time
append(1, netmask.bytes) // subnet mask
append(3, server.bytes) // router
append(6, server.bytes) // DNS
append(26, [UInt8(mtu >> 8), UInt8(mtu & 0xFF)]) // interface MTU
append(1, netmask.bytes) // subnet mask
append(3, server.bytes) // router
append(6, server.bytes) // DNS
append(26, [UInt8(mtu >> 8), UInt8(mtu & 0xFF)]) // interface MTU
options.append(255)
// Pad to the minimum BOOTP payload so short replies stay well-formed.
while (out.count + options.count) < 300 { options.append(0) }
while (out.count + options.count) < 300 {
options.append(0)
}
return out + options
}
}
@@ -211,7 +219,9 @@ final class VPhoneUserspaceNetworkResponder {
self.configuration = configuration
}
var netmask: VPhoneIPv4Address { VPhoneIPv4Address(255, 255, 255, 0) }
var netmask: VPhoneIPv4Address {
VPhoneIPv4Address(255, 255, 255, 0)
}
func handle(_ frame: [UInt8]) -> VPhoneUserspaceNetworkOutcome {
guard let ethernet = VPhoneEthernetFrame(bytes: frame) else { return .drop }
@@ -302,7 +312,9 @@ final class VPhoneUserspaceNetworkResponder {
// DHCP is the one UDP exchange this side finishes itself: the guest is
// asking us, by definition.
if let reply = respondToDHCP(packet, datagram) { return .reply(reply) }
if let reply = respondToDHCP(packet, datagram) {
return .reply(reply)
}
// Everything else is egress. The answer has to reach the guest, so we
// need its MAC — and until it has sent something we do not have it.
@@ -320,7 +332,7 @@ final class VPhoneUserspaceNetworkResponder {
}
/// Our own DHCP server, or nil when this is not a request we answer.
private func respondToDHCP(_ packet: VPhoneIPv4Packet, _ datagram: VPhoneUDPDatagram) -> [UInt8]? {
private func respondToDHCP(_: VPhoneIPv4Packet, _ datagram: VPhoneUDPDatagram) -> [UInt8]? {
guard datagram.destinationPort == VPhoneDHCPMessage.serverPort,
let request = VPhoneDHCPMessage(bytes: datagram.payload)
else { return nil }
@@ -82,12 +82,12 @@ struct VPhoneInternetProtocolTests {
let source = VPhoneIPv4Address(192, 168, 127, 3)
let destination = VPhoneIPv4Address(142, 250, 1, 1)
let segment = VPhoneTCPSegment(
sourcePort: 51_000,
sourcePort: 51000,
destinationPort: 443,
sequenceNumber: 1234,
acknowledgmentNumber: 5678,
flags: VPhoneTCPFlags.ack | VPhoneTCPFlags.psh,
windowSize: 65_535,
windowSize: 65535,
payload: Array("hello".utf8),
)
let bytes = segment.bytes(source: source, destination: destination)
@@ -109,7 +109,7 @@ struct VPhoneInternetProtocolTests {
sequenceNumber: 0,
acknowledgmentNumber: 0,
flags: VPhoneTCPFlags.syn,
windowSize: 65_535,
windowSize: 65535,
advertisedMSS: 1460,
advertisedWindowScale: 7,
)
@@ -22,7 +22,9 @@ struct VPhoneTCPForwarderTests {
private let lock = NSLock()
private var value: Value
init(_ value: Value) { self.value = value }
init(_ value: Value) {
self.value = value
}
func withLock<Result>(_ body: (inout Value) -> Result) -> Result {
lock.lock()
@@ -48,8 +50,8 @@ struct VPhoneTCPForwarderTests {
init(guestPort: UInt16, destinationPort: UInt16, mss: Int = 1460) {
self.guestPort = guestPort
let queue = DispatchQueue(label: "forwarder-tests.\(guestPort)")
let recorded = self.recorded
let acknowledgesImmediately = self.acknowledgesImmediately
let recorded = recorded
let acknowledgesImmediately = acknowledgesImmediately
let flow = VPhoneTCPFlow(
sourceAddress: VPhoneUserspaceNetworkConfiguration.default.guestAddress,
sourcePort: guestPort,
@@ -72,7 +74,7 @@ struct VPhoneTCPForwarderTests {
sequenceNumber: segment.sequenceNumber,
acknowledgmentNumber: segment.sequenceNumber &+ UInt32(segment.payload.count),
flags: VPhoneTCPFlags.ack,
windowSize: 65_535,
windowSize: 65535,
)
// Async rather than direct: `receive` re-enters the same object,
// and this way the recursion depth stays bounded.
@@ -82,13 +84,19 @@ struct VPhoneTCPForwarderTests {
_ = mss
}
func acknowledgeImmediately() { acknowledgesImmediately.withLock { $0 = true } }
func acknowledgeImmediately() {
acknowledgesImmediately.withLock { $0 = true }
}
/// What the forwarder has put on the wire, oldest first.
var sent: [VPhoneTCPSegment] { recorded.withLock { $0 } }
var sent: [VPhoneTCPSegment] {
recorded.withLock { $0 }
}
/// Our ISN, read off the SYN-ACK the only way any peer could.
var ourISN: UInt32? { sent.first { $0.hasSYN && $0.hasACK }?.sequenceNumber }
var ourISN: UInt32? {
sent.first { $0.hasSYN && $0.hasACK }?.sequenceNumber
}
/// Bytes of *distinct* sequence numbers. A retransmission repeats a range
/// rather than extending it, so counting transmissions would overcount.
@@ -109,9 +117,17 @@ struct VPhoneTCPForwarderTests {
return unique.sorted { $0.key < $1.key }.map { (sequence: $0.key, count: $0.value) }
}
func feed(_ segment: VPhoneTCPSegment) { queue.sync { forwarder.receive(segment, for: flow) } }
func start() { queue.sync { forwarder.start() } }
func stop() { queue.sync { forwarder.stop() } }
func feed(_ segment: VPhoneTCPSegment) {
queue.sync { forwarder.receive(segment, for: flow) }
}
func start() {
queue.sync { forwarder.start() }
}
func stop() {
queue.sync { forwarder.stop() }
}
/// Wait for something a background socket is responsible for.
///
@@ -121,8 +137,10 @@ struct VPhoneTCPForwarderTests {
func waitUntil(_ what: String, timeout: TimeInterval = 8, _ predicate: () -> Bool) -> Bool {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if predicate() { return true }
usleep(10_000)
if predicate() {
return true
}
usleep(10000)
}
Issue.record("timed out waiting for \(what)")
return false
@@ -180,31 +198,42 @@ struct VPhoneTCPForwarderTests {
}
/// Bytes pulled out of the socket by the session.
var bytesReceived: Int { received.withLock { $0 } }
var bytesReceived: Int {
received.withLock { $0 }
}
/// Whether the session has returned.
var isFinished: Bool { finished.withLock { $0 } }
var isFinished: Bool {
finished.withLock { $0 }
}
/// Read and count everything the guest sends.
func drain(_ client: Int32) {
var buffer = [UInt8](repeating: 0, count: 65_536)
var buffer = [UInt8](repeating: 0, count: 65536)
while true {
let read = recv(client, &buffer, buffer.count, 0)
if read <= 0 { return }
if read <= 0 {
return
}
received.withLock { $0 += read }
}
}
/// Read and count, stopping early once `target` bytes have arrived.
func drain(_ client: Int32, until target: Int) {
var buffer = [UInt8](repeating: 0, count: 65_536)
var buffer = [UInt8](repeating: 0, count: 65536)
while bytesReceived < target {
let read = recv(client, &buffer, buffer.count, 0)
if read <= 0 { return }
if read <= 0 {
return
}
received.withLock { $0 += read }
}
}
func stop() { close(listener) }
func stop() {
close(listener)
}
enum Error: Swift.Error {
case socketFailed
@@ -218,7 +247,7 @@ struct VPhoneTCPForwarderTests {
private func handshake(
_ harness: Harness,
guestSequence: UInt32 = 1000,
window: UInt16 = 65_535,
window: UInt16 = 65535,
mss: Int = 1460,
windowScale: Int? = nil,
) throws -> UInt32 {
@@ -246,7 +275,7 @@ struct VPhoneTCPForwarderTests {
}
/// Ask the host for data: a push with the peek byte.
private func request(_ harness: Harness, acknowledged: UInt32, window: UInt16 = 65_535) {
private func request(_ harness: Harness, acknowledged: UInt32, window: UInt16 = 65535) {
harness.feed(VPhoneTCPSegment(
sourcePort: harness.guestPort,
destinationPort: harness.flow.destinationPort,
@@ -272,14 +301,16 @@ struct VPhoneTCPForwarderTests {
var sent = 0
while sent < payload.count {
let written = payload.withUnsafeBytes { send(client, $0.baseAddress, $0.count, 0) }
if written <= 0 { break }
if written <= 0 {
break
}
sent += written
}
_ = server.drain(client)
}
defer { server.stop() }
let harness = Harness(guestPort: 51_001, destinationPort: server.port)
let harness = Harness(guestPort: 51001, destinationPort: server.port)
harness.start()
defer { harness.stop() }
@@ -312,7 +343,9 @@ struct VPhoneTCPForwarderTests {
))
harness.waitUntil("more data", timeout: 3) { harness.sentDataBytes > before }
acknowledged = isn &+ 1 &+ UInt32(harness.sentDataBytes)
if harness.sentDataBytes >= total { break }
if harness.sentDataBytes >= total {
break
}
}
#expect(harness.sentDataBytes >= total, "stalled at \(harness.sentDataBytes)B of \(total)B")
@@ -338,7 +371,7 @@ struct VPhoneTCPForwarderTests {
}
defer { server.stop() }
let harness = Harness(guestPort: 51_002, destinationPort: server.port)
let harness = Harness(guestPort: 51002, destinationPort: server.port)
harness.start()
defer { harness.stop() }
@@ -356,7 +389,7 @@ struct VPhoneTCPForwarderTests {
sequenceNumber: sequence,
acknowledgmentNumber: isn &+ 1,
flags: VPhoneTCPFlags.ack | VPhoneTCPFlags.psh,
windowSize: 65_535,
windowSize: 65535,
payload: Array(chunk.prefix(take)),
))
sequence &+= UInt32(take)
@@ -384,13 +417,13 @@ struct VPhoneTCPForwarderTests {
/// option 3 in our SYN-ACK must keep its window under 64 KiB however much
/// buffer it has. That ceiling, divided by the round-trip time, is the most
/// the connection can ever carry.
@Test func `window scaling is negotiated and the guest's field read at its true size`() async throws {
@Test func `window scaling is negotiated and the guest's field read at its true size`() throws {
// The option survives a round trip before anything else is meaningful.
let offered = VPhoneTCPSegment(
sourcePort: 1, destinationPort: 2,
sequenceNumber: 0, acknowledgmentNumber: 0,
flags: VPhoneTCPFlags.syn,
windowSize: 65_535,
windowSize: 65535,
advertisedMSS: 1460,
advertisedWindowScale: 7,
)
@@ -404,7 +437,7 @@ struct VPhoneTCPForwarderTests {
#expect(encoded[12] >> 4 == 7, "the option list must pad to a 32-bit boundary")
let total = 400_000
let blob = [UInt8](repeating: 0x21, count: 65_536)
let blob = [UInt8](repeating: 0x21, count: 65536)
let server = try LoopbackServer { client, server in
var buffer = [UInt8](repeating: 0, count: 4096)
_ = recv(client, &buffer, buffer.count, 0)
@@ -413,14 +446,16 @@ struct VPhoneTCPForwarderTests {
let written = blob.withUnsafeBytes { raw in
send(client, raw.baseAddress, min(raw.count, total - sent), 0)
}
if written <= 0 { break }
if written <= 0 {
break
}
sent += written
}
_ = server.drain(client)
}
defer { server.stop() }
let harness = Harness(guestPort: 51_003, destinationPort: server.port)
let harness = Harness(guestPort: 51003, destinationPort: server.port)
harness.start()
defer { harness.stop() }
@@ -454,7 +489,7 @@ struct VPhoneTCPForwarderTests {
/// stands. The link to the guest drops nothing by itself, but that is not the
/// only way a segment goes unacknowledged, and with no copy kept there is
/// nothing to send again -- the peer just waits for a timeout.
@Test func `data the guest does not acknowledge is sent again`() async throws {
@Test func `data the guest does not acknowledge is sent again`() throws {
let payload = [UInt8](repeating: 0x77, count: 4000)
let server = try LoopbackServer { client, server in
var buffer = [UInt8](repeating: 0, count: 4096)
@@ -464,7 +499,7 @@ struct VPhoneTCPForwarderTests {
}
defer { server.stop() }
let harness = Harness(guestPort: 51_004, destinationPort: server.port)
let harness = Harness(guestPort: 51004, destinationPort: server.port)
harness.start()
defer { harness.stop() }
@@ -488,7 +523,7 @@ struct VPhoneTCPForwarderTests {
/// change to windowing or retransmission has not made it one.
@Test func `a promptly acknowledging guest gets the payload fast`() throws {
let total = 4 << 20
let blob = [UInt8](repeating: 0x33, count: 65_536)
let blob = [UInt8](repeating: 0x33, count: 65536)
let server = try LoopbackServer { client, server in
var buffer = [UInt8](repeating: 0, count: 4096)
_ = recv(client, &buffer, buffer.count, 0)
@@ -497,14 +532,16 @@ struct VPhoneTCPForwarderTests {
let written = blob.withUnsafeBytes { raw in
send(client, raw.baseAddress, min(raw.count, total - sent), 0)
}
if written <= 0 { break }
if written <= 0 {
break
}
sent += written
}
_ = server.drain(client, until: total)
}
defer { server.stop() }
let harness = Harness(guestPort: 51_005, destinationPort: server.port)
let harness = Harness(guestPort: 51005, destinationPort: server.port)
harness.acknowledgeImmediately()
harness.start()
defer { harness.stop() }
@@ -40,8 +40,13 @@ struct VPhoneUserspaceNetworkIntegrationTests {
)
}
func start() { network.start() }
func stop() { network.stop() }
func start() {
network.start()
}
func stop() {
network.stop()
}
/// Put a frame on the wire the way the attachment would.
func write(_ frame: [UInt8]) {
@@ -56,8 +61,10 @@ struct VPhoneUserspaceNetworkIntegrationTests {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
let count = recv(descriptor, &buffer, buffer.count, 0)
if count > 0 { return Array(buffer[0 ..< count]) }
usleep(5_000)
if count > 0 {
return Array(buffer[0 ..< count])
}
usleep(5000)
}
return nil
}
@@ -70,7 +77,9 @@ struct VPhoneUserspaceNetworkIntegrationTests {
private let lock = NSLock()
private var value: Value
init(_ value: Value) { self.value = value }
init(_ value: Value) {
self.value = value
}
func withLock<Result>(_ body: (inout Value) -> Result) -> Result {
lock.lock()
@@ -231,7 +240,7 @@ struct VPhoneUserspaceNetworkIntegrationTests {
let deadline = Date().addingTimeInterval(5)
while Date() < deadline, !returned.withLock({ $0 }) {
usleep(10_000)
usleep(10000)
}
#expect(
returned.withLock { $0 },
@@ -139,7 +139,9 @@ struct VPhoneUserspaceNetworkTests {
while index + 1 < options.count, options[index] != 255 {
let length = Int(options[index + 1])
guard index + 2 + length <= options.count else { return nil }
if options[index] == code { return Array(options[(index + 2) ..< (index + 2 + length)]) }
if options[index] == code {
return Array(options[(index + 2) ..< (index + 2 + length)])
}
index += 2 + length
}
return nil
@@ -280,7 +282,7 @@ struct VPhoneUserspaceNetworkTests {
/// UDP that is not DHCP is egress rather than something this side answers.
/// The flow it names has to carry both ends and the guest's MAC, because the
/// answer is built later, by the forwarder, with no access to this type.
@Test func `UDP for somewhere else becomes a forward`() throws {
@Test func `UDP for somewhere else becomes a forward`() {
let responder = responder()
// Learn the MAC first, the way a real guest's traffic would.
_ = responder.handle(arpFrame(targeting: configuration.hostAddress))
@@ -325,7 +327,7 @@ struct VPhoneUserspaceNetworkTests {
/// TCP is terminated, not relayed, so the whole segment goes to the
/// forwarder along with the flow it belongs to.
@Test func `TCP segment becomes a forwardTCP`() throws {
@Test func `TCP segment becomes a forwardTCP`() {
let responder = responder()
let syn = VPhoneTCPSegment(
sourcePort: 51000, destinationPort: 80, sequenceNumber: 1000,
@@ -416,7 +418,9 @@ private extension VPhoneUserspaceNetworkResponder {
/// Added when `respond(to:)` became `handle(_:)` returning an outcome, so
/// the frame-level tests above kept reading the same way.
func respond(to frame: [UInt8]) -> [UInt8]? {
if case let .reply(reply) = handle(frame) { return reply }
if case let .reply(reply) = handle(frame) {
return reply
}
return nil
}
}