nx_pathstripe_test.nx source
↩ module page · 90 lines · 3785 B
1// nx_pathstripe_test.nx -- 1:1 KAT for multi-path striping / seamless
2// protection switching (nx_pathstripe.nx). Proves SMPTE-2022-7-class
3// behaviour: with two paths whose losses DISJOINT, every packet is
4// delivered exactly once in order with ZERO recovery time; a packet lost
5// on ALL paths is honestly reported lost (never blocks); duplicates are
6// deduped; and top-K striping picks the best-quality paths.
7//
8// expect_exit: 0
9// license_tier: ORIGINAL
10
11import "nx_pathstripe.nx"
12
13const PW: i64 = 64 // reorder window
14const PN: i64 = 16 // packets in the stream
15
16func dropped(seq: i64, d0: i64, d1: i64, d2: i64) -> i64 {
17 if seq == d0 { return 1 }
18 if seq == d1 { return 1 }
19 if seq == d2 { return 1 }
20 return 0
21}
22// feed one path's stream (all seqs except its 3 drops), draining as we go.
23func feed_path(rx: *StripeRx, slots: *i64, log: *i64, d0: i64, d1: i64, d2: i64) -> i64 {
24 var s: i64 = 0
25 while s < PN {
26 if dropped(s, d0, d1, d2) == 0 {
27 stripe_rx_arrive(rx, slots, PW, s)
28 stripe_rx_drain(rx, slots, PW, log)
29 }
30 s = s + 1
31 }
32 return 0
33}
34func mk_flow(srtt: i64, jit: i64, loss_q16: i64) -> *LinkFlow {
35 let f: *LinkFlow = lq_flow_new()
36 f.srtt_x8 = srtt << LQ_SRTT_SH
37 f.jitter_x16 = jit << LQ_JIT_SH
38 f.loss_frac_q16 = loss_q16
39 return f
40}
41
42func main() -> i64 {
43 let slots: *i64 = sys_mmap(PW * 8) as *i64
44 let log: *i64 = sys_mmap(64 * 8) as *i64
45 let rx: *StripeRx = sys_mmap(STRIPE_RX_BYTES) as *StripeRx
46
47 // ---- T1: DISJOINT drops -> seamless, zero loss, in-order, deduped ----
48 stripe_rx_init(rx, slots, PW)
49 feed_path(rx, slots, log, 2, 5, 9) // path A drops {2,5,9}
50 feed_path(rx, slots, log, 3, 6, 11) // path B drops {3,6,11} (disjoint)
51 stripe_rx_finalize(rx, slots, PW, PN, log)
52 if rx.delivered != 16 { return 1 }
53 if rx.lost != 0 { return 2 } // every loss covered by the other path
54 var i: i64 = 0
55 while i < PN { if log[i] != i { return 3 } i = i + 1 } // exactly 0..15, once, in order
56
57 // ---- T2: lost on ALL paths -> honestly lost, rest delivered ----
58 stripe_rx_init(rx, slots, PW)
59 feed_path(rx, slots, log, 5, 5, 5) // path A drops {5}
60 feed_path(rx, slots, log, 5, 5, 5) // path B also drops {5}
61 stripe_rx_finalize(rx, slots, PW, PN, log)
62 if rx.delivered != 15 { return 4 }
63 if rx.lost != 1 { return 5 }
64 if log[5] != 6 { return 6 } // seq 5 skipped -> 6th delivered is seq 6
65
66 // ---- T3: duplicate copies deduped, delivered once ----
67 stripe_rx_init(rx, slots, PW)
68 if stripe_rx_arrive(rx, slots, PW, 0) != 1 { return 7 } // first copy accepted
69 if stripe_rx_arrive(rx, slots, PW, 0) != 0 { return 8 } // second copy = dup
70 stripe_rx_drain(rx, slots, PW, log)
71 if rx.delivered != 1 { return 9 }
72
73 // ---- T4: top-K striping picks the best-quality paths ----
74 let flows: *i64 = sys_mmap(4 * 8) as *i64
75 flows[0] = (mk_flow(20, 0, 0)) as i64 // score 96
76 flows[1] = (mk_flow(200, 5, 0)) as i64 // score 50
77 flows[2] = (mk_flow(75, 5, 0)) as i64 // score 75
78 flows[3] = (mk_flow(300, 30, 6554)) as i64 // score 0
79 let topk: *i64 = sys_mmap(4 * 8) as *i64
80 let nk: i64 = stripe_select_topk(flows, 4, 2, topk)
81 if nk != 2 { return 10 }
82 if topk[0] != 0 { return 11 } // best = path0 (96)
83 if topk[1] != 2 { return 12 } // 2nd = path2 (75)
84 if stripe_k_for_loss(0, 4) != 1 { return 13 }
85 if stripe_k_for_loss(3, 4) != 2 { return 14 }
86 if stripe_k_for_loss(10, 4) != 3 { return 15 }
87
88 sys_write(1, "PATHSTRIPE KAT PASS (seamless zero-loss failover on disjoint loss + honest all-lost + dedup + top-K)\n", 100)
89 return 0
90}