|
| 1 | +// Standalone verification for AudioRED (adaptive depth). Compiles the ACTUAL |
| 2 | +// production file (AudioRED.swift) plus this main — no re-implementation, so it |
| 3 | +// cannot drift. Round-trips through naked wire bytes exactly like the app. |
| 4 | +// swiftc AudioRED.swift main.swift -o /tmp/audio_red && /tmp/audio_red |
| 5 | +import Foundation |
| 6 | + |
| 7 | +func frame(_ i: Int) -> Data { Data("OPUS_FRAME_\(i)".utf8) } |
| 8 | + |
| 9 | +var failures = 0 |
| 10 | +func check(_ cond: Bool, _ label: String) { |
| 11 | + print("\(cond ? "PASS" : "FAIL") \(label)") |
| 12 | + if !cond { failures += 1 } |
| 13 | +} |
| 14 | + |
| 15 | +// Sender keeps a newest-first ring of the last `depth` frames and packs them; |
| 16 | +// `drop` is the set of dropped sequence indices; returns the frames the receiver played. |
| 17 | +func run(n: Int, drop: Set<Int>, depth: Int) -> [Data] { |
| 18 | + var rx = AudioREDReceiver() |
| 19 | + var ring: [Data] = [] |
| 20 | + var played: [Data] = [] |
| 21 | + for i in 0..<n { |
| 22 | + ring.insert(frame(i), at: 0) |
| 23 | + if ring.count > depth { ring.removeLast() } |
| 24 | + let pkt = AudioRED.pack(seq: UInt8(i & 0xFF), frames: ring) |
| 25 | + if drop.contains(i) { continue } |
| 26 | + guard let (s, frames) = AudioRED.parse(pkt) else { played.append(Data("PARSE_FAIL".utf8)); continue } |
| 27 | + played.append(contentsOf: rx.receive(seq: s, frames: frames)) |
| 28 | + } |
| 29 | + return played |
| 30 | +} |
| 31 | + |
| 32 | +print("== pack/parse round-trip (variable depth, incl max-len) ==") |
| 33 | +for frames in [[frame(1)], [frame(9), frame(8)], [frame(5), frame(4), frame(3)], |
| 34 | + [Data(repeating: 0xAB, count: 300), frame(2)]] { |
| 35 | + let (s, fs) = AudioRED.parse(AudioRED.pack(seq: 200, frames: frames))! |
| 36 | + check(s == 200 && fs == Array(frames.prefix(AudioRED.maxFrames)), "roundtrip depth=\(frames.count)") |
| 37 | +} |
| 38 | +check(AudioRED.parse(Data([0x01])) == nil, "truncated header rejected") |
| 39 | +check(AudioRED.parse(Data([0x01, 0x02, 0xFF, 0x00])) == nil, "declared frame past end rejected") |
| 40 | +check(AudioRED.parse(Data([0x01, 0x09])) == nil, "count > maxFrames rejected") |
| 41 | + |
| 42 | +print("\n== depth 2 (baseline: survive 1 isolated loss) ==") |
| 43 | +check(run(n: 10, drop: [], depth: 2) == (0..<10).map(frame), "no loss -> all in order") |
| 44 | +check(run(n: 10, drop: [4], depth: 2) == (0..<10).map(frame), "1 isolated loss recovered") |
| 45 | +check(run(n: 10, drop: [4, 5], depth: 2) == [0,1,2,3,5,6,7,8,9].map(frame), "2 CONSECUTIVE -> only #5 recovered (1-deep)") |
| 46 | +check(run(n: 12, drop: [3, 7], depth: 2) == (0..<12).map(frame), "2 spaced losses both recovered") |
| 47 | + |
| 48 | +print("\n== depth 3 (survive a 2-loss BURST — the new capability) ==") |
| 49 | +check(run(n: 10, drop: [], depth: 3) == (0..<10).map(frame), "no loss -> all in order") |
| 50 | +check(run(n: 10, drop: [4], depth: 3) == (0..<10).map(frame), "1 isolated loss recovered") |
| 51 | +check(run(n: 10, drop: [4, 5], depth: 3) == (0..<10).map(frame), "2 CONSECUTIVE losses BOTH recovered") |
| 52 | +check(run(n: 12, drop: [4, 5, 6], depth: 3) == [0,1,2,3,5,6,7,8,9,10,11].map(frame), "3 consecutive -> #5,#6 recovered, #4 lost (2-deep ceiling)") |
| 53 | + |
| 54 | +print("\n== adaptive: depth changes MID-STREAM without breaking order ==") |
| 55 | +do { |
| 56 | + var rx = AudioREDReceiver(); var ring: [Data] = []; var played: [Data] = [] |
| 57 | + for i in 0..<12 { |
| 58 | + let depth = i < 6 ? 2 : 3 // sender bumps depth at frame 6 |
| 59 | + ring.insert(frame(i), at: 0); if ring.count > depth { ring.removeLast() } |
| 60 | + let pkt = AudioRED.pack(seq: UInt8(i), frames: ring) |
| 61 | + if i == 8 || i == 9 { continue } // a 2-burst while at depth 3 |
| 62 | + let (s, fs) = AudioRED.parse(pkt)! |
| 63 | + played += rx.receive(seq: s, frames: fs) |
| 64 | + } |
| 65 | + check(played == (0..<12).map(frame), "depth 2->3 mid-call, a 2-burst at depth 3 fully recovered") |
| 66 | +} |
| 67 | + |
| 68 | +print("\n== duplicate not played twice; late/reordered dropped ==") |
| 69 | +do { |
| 70 | + var rx = AudioREDReceiver(); var played: [Data] = [] |
| 71 | + played += rx.receive(seq: 0, frames: [frame(0)]) |
| 72 | + played += rx.receive(seq: 1, frames: [frame(1), frame(0)]) |
| 73 | + played += rx.receive(seq: 1, frames: [frame(1), frame(0)]) // duplicate |
| 74 | + check(played == [frame(0), frame(1)], "seq 1 twice -> played once") |
| 75 | + let before = played.count |
| 76 | + for i in 2...5 { played += rx.receive(seq: UInt8(i), frames: [frame(i), frame(i-1)]) } |
| 77 | + let afterFwd = played.count |
| 78 | + played += rx.receive(seq: 3, frames: [frame(3), frame(2)]) // arrives late |
| 79 | + check(played.count == afterFwd, "seq 3 after seq 5 -> ignored") |
| 80 | + _ = before |
| 81 | +} |
| 82 | + |
| 83 | +print("\n== u8 wraparound with a loss across 255->0 (depth 2) ==") |
| 84 | +do { |
| 85 | + var rx = AudioREDReceiver(); var played: [Data] = [] |
| 86 | + played += rx.receive(seq: 254, frames: [frame(254), frame(253)]) |
| 87 | + played += rx.receive(seq: 255, frames: [frame(255), frame(254)]) |
| 88 | + // seq 0 dropped |
| 89 | + played += rx.receive(seq: 1, frames: [frame(257), frame(256)]) // 256==seq0, 257==seq1 |
| 90 | + check(played == [frame(254), frame(255), frame(256), frame(257)], "loss at wrap recovered") |
| 91 | +} |
| 92 | + |
| 93 | +print("\n== long outage resyncs immediately at any depth ==") |
| 94 | +func outageDrop(_ outage: Int, _ depth: Int) -> Int { |
| 95 | + var rx = AudioREDReceiver(); var ring: [Data] = [] |
| 96 | + for i in 0..<20 { ring.insert(frame(i), at: 0); if ring.count > depth { ring.removeLast() }; _ = rx.receive(seq: UInt8(i & 0xFF), frames: ring) } |
| 97 | + for i in 20..<(20+outage) { ring.insert(frame(i), at: 0); if ring.count > depth { ring.removeLast() } } |
| 98 | + var dropped = 0 |
| 99 | + for k in 0..<300 { |
| 100 | + let i = 20 + outage + k |
| 101 | + ring.insert(frame(i), at: 0); if ring.count > depth { ring.removeLast() } |
| 102 | + if rx.receive(seq: UInt8(i & 0xFF), frames: ring).isEmpty { dropped += 1 } else { break } |
| 103 | + } |
| 104 | + return dropped |
| 105 | +} |
| 106 | +for o in [130, 200, 255] { check(outageDrop(o, 3) <= 1, "outage \(o) (depth 3) resumes within 1 packet") } |
| 107 | + |
| 108 | +print("\n== exhaustive: every interior single-loss position recovered, depth 2 AND 3 ==") |
| 109 | +var ok2 = true, ok3 = true |
| 110 | +for k in 1..<19 { |
| 111 | + if run(n: 20, drop: [k], depth: 2) != (0..<20).map(frame) { ok2 = false } |
| 112 | + if run(n: 20, drop: [k], depth: 3) != (0..<20).map(frame) { ok3 = false } |
| 113 | +} |
| 114 | +check(ok2 && ok3, "all interior single-loss positions recovered at depth 2 and depth 3") |
| 115 | + |
| 116 | +print("\n\(failures == 0 ? "ALL PASS" : "\(failures) FAILURE(S)")") |
| 117 | +exit(failures == 0 ? 0 : 1) |
0 commit comments