T-iOS-1: ios/ XcodeGen project (iOS 17, Swift 6 strict concurrency, ATS per PLAN §5.2), 5 SPM package shells, CI skeleton T-iOS-2: Origin spike vs real server — URLSessionWebSocketTask custom Origin CONFIRMED (no Starscream); 16MiB replay + EMSGSIZE(40) errno correction written back to plan T-iOS-3: WireProtocol frozen contract, 59 tests, 100% line coverage, cross-impl vectors vs src/protocol.ts via tsx T-iOS-4: FakeTransport/FakeClock/FakeHTTPTransport doubles Verify: independent agent re-ran all acceptance — 6/6 PASS
72 lines
2.8 KiB
Swift
72 lines
2.8 KiB
Swift
import Foundation
|
|
import WireProtocol
|
|
|
|
/// Deterministic RNG for the roundtrip property test (reproducible failures:
|
|
/// the seed is baked into the test; change it only with a note in the test).
|
|
struct SplitMix64: RandomNumberGenerator {
|
|
private var state: UInt64
|
|
|
|
init(seed: UInt64) {
|
|
state = seed
|
|
}
|
|
|
|
mutating func next() -> UInt64 {
|
|
state &+= 0x9E37_79B9_7F4A_7C15
|
|
var z = state
|
|
z = (z ^ (z >> 30)) &* 0xBF58_476D_1CE4_E5B9
|
|
z = (z ^ (z >> 27)) &* 0x94D0_49BB_1331_11EB
|
|
return z ^ (z >> 31)
|
|
}
|
|
}
|
|
|
|
enum MessageGen {
|
|
/// Character pool that stresses the JSON escaper: quotes, backslashes,
|
|
/// C0 control bytes (ESC / CR / TAB / ^C / NUL), CJK, emoji, plain ASCII.
|
|
private static let stressPool: [Character] = [
|
|
"a", "Z", "0", " ", "\"", "\\", "/", "{", "}",
|
|
"\u{1B}", "\r", "\n", "\t", "\u{03}", "\u{00}", "\u{7F}",
|
|
"中", "文", "終", "端", "🚀", "🧪", "é", "ß",
|
|
]
|
|
|
|
static func randomString(using rng: inout some RandomNumberGenerator, maxLength: Int = 48) -> String {
|
|
let length = Int.random(in: 0...maxLength, using: &rng)
|
|
return String((0..<length).map { _ in stressPool.randomElement(using: &rng)! })
|
|
}
|
|
|
|
static func randomAbsolutePath(using rng: inout some RandomNumberGenerator) -> String {
|
|
"/" + randomString(using: &rng, maxLength: 24)
|
|
}
|
|
|
|
/// Seeded UUID v4 (version nibble 4, variant 10xx) — passes SESSION_ID_RE.
|
|
static func randomUUIDv4(using rng: inout some RandomNumberGenerator) -> UUID {
|
|
var bytes = (0..<16).map { _ in UInt8.random(in: 0...255, using: &rng) }
|
|
bytes[6] = (bytes[6] & 0x0F) | 0x40
|
|
bytes[8] = (bytes[8] & 0x3F) | 0x80
|
|
return UUID(uuid: (
|
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
|
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]
|
|
))
|
|
}
|
|
|
|
static func randomClientMessage(using rng: inout some RandomNumberGenerator) -> ClientMessage {
|
|
switch Int.random(in: 0...4, using: &rng) {
|
|
case 0:
|
|
let sessionId = Bool.random(using: &rng) ? randomUUIDv4(using: &rng) : nil
|
|
let cwd = Bool.random(using: &rng) ? randomAbsolutePath(using: &rng) : nil
|
|
return .attach(sessionId: sessionId, cwd: cwd)
|
|
case 1:
|
|
return .input(data: randomString(using: &rng))
|
|
case 2:
|
|
return .resize(
|
|
cols: Int.random(in: 1...1000, using: &rng),
|
|
rows: Int.random(in: 1...1000, using: &rng)
|
|
)
|
|
case 3:
|
|
let mode = Bool.random(using: &rng) ? ApproveMode.allCases.randomElement(using: &rng) : nil
|
|
return .approve(mode: mode)
|
|
default:
|
|
return .reject
|
|
}
|
|
}
|
|
}
|