nx_fec_xor.nx source
↩ module page · 163 lines · 6027 B
1// nx_fec_xor.nx -- sovereign XOR forward-error-correction (FlexFEC family,
2// RFC 8627 re-implemented from spec). Closes the #1 shared gap vs the
3// very best: loss-resilience, behind top esports netcode AND missing for
4// NASA/aerospace-critical streaming. Pure XOR -> integer-only, no field
5// math, branch-light, sovereign (imports only nx_syscalls).
6//
7// Same machinery, three uses:
8// - row (1-D non-interleaved): recovers 1 RANDOM loss per row.
9// - column (1-D interleaved): recovers a BURST of up to D consecutive
10// (they land in distinct columns).
11// - 2-D (row+column): iterative peeling recovers most MIXED loss.
12// - K=1 row == the game REDUNDANT-INPUT window (send last N inputs).
13//
14// THE EXCEED: redundancy is sized adaptively from the REAL per-flow loss
15// estimator (nx_linkqual lq_loss_pct), not a fixed cvar -- caller passes
16// loss_pct to fec_scheme_for_loss. Generalizes the shipped nx_voice_fec
17// redundant-copy concept (CLAUDE.md 15 / review-existing-functionality).
18//
19// Layout: source packets in a flat buffer, packet i at src + i*S (each
20// padded to S bytes). Grid is L columns x D rows, K = L*D source packets,
21// row j cell i = index j*L + i. Row repairs: D x S. Col repairs: L x S.
22//
23// license_tier: ORIGINAL
24
25import "nx_syscalls.nx"
26
27// repair schemes
28const FEC_NONE: i64 = 0
29const FEC_ROW: i64 = 1
30const FEC_2D: i64 = 2
31
32func fec_pkt(buf: *u8, i: i64, S: i64) -> *u8 { return ((buf as i64) + i * S) as *u8 }
33
34func fec_zero(p: *u8, S: i64) -> i64 {
35 var b: i64 = 0
36 while b < S { p[b] = 0 as u8; b = b + 1 }
37 return 0
38}
39// dst ^= src, S bytes.
40func fec_xor_into(dst: *u8, src: *u8, S: i64) -> i64 {
41 var b: i64 = 0
42 while b < S {
43 dst[b] = (((dst[b] as i64) ^ (src[b] as i64)) & 0xff) as u8
44 b = b + 1
45 }
46 return 0
47}
48
49// ---- encode ----
50func fec_encode_rows(src: *u8, L: i64, D: i64, S: i64, out_rows: *u8) -> i64 {
51 var j: i64 = 0
52 while j < D {
53 let rr: *u8 = fec_pkt(out_rows, j, S)
54 fec_zero(rr, S)
55 var i: i64 = 0
56 while i < L { fec_xor_into(rr, fec_pkt(src, j * L + i, S), S); i = i + 1 }
57 j = j + 1
58 }
59 return 0
60}
61func fec_encode_cols(src: *u8, L: i64, D: i64, S: i64, out_cols: *u8) -> i64 {
62 var k: i64 = 0
63 while k < L {
64 let cc: *u8 = fec_pkt(out_cols, k, S)
65 fec_zero(cc, S)
66 var j: i64 = 0
67 while j < D { fec_xor_into(cc, fec_pkt(src, j * L + k, S), S); j = j + 1 }
68 k = k + 1
69 }
70 return 0
71}
72
73// ---- decode (2-D iterative peeling) ----
74// present[i]=1 if source packet i survived; row_present[j]/col_present[k]=1
75// if that repair survived. Recovers in place; returns # still-missing
76// source packets (0 = full recovery; >0 = honest unrecoverable, no corrupt).
77func fec_decode_2d(src: *u8, L: i64, D: i64, S: i64, present: *i64,
78 rows: *u8, row_present: *i64, cols: *u8, col_present: *i64) -> i64 {
79 var progress: i64 = 1
80 while progress == 1 {
81 progress = 0
82 // rows: any row with exactly one missing cell + its repair present
83 var j: i64 = 0
84 while j < D {
85 if row_present[j] == 1 {
86 var miss: i64 = 0 - 1
87 var cnt: i64 = 0
88 var i: i64 = 0
89 while i < L {
90 let idx: i64 = j * L + i
91 if present[idx] == 0 { cnt = cnt + 1; miss = idx }
92 i = i + 1
93 }
94 if cnt == 1 {
95 let dst: *u8 = fec_pkt(src, miss, S)
96 fec_zero(dst, S)
97 fec_xor_into(dst, fec_pkt(rows, j, S), S)
98 var i2: i64 = 0
99 while i2 < L {
100 let idx2: i64 = j * L + i2
101 if idx2 != miss { fec_xor_into(dst, fec_pkt(src, idx2, S), S) }
102 i2 = i2 + 1
103 }
104 present[miss] = 1
105 progress = 1
106 }
107 }
108 j = j + 1
109 }
110 // columns: any column with exactly one missing cell + its repair present
111 var k: i64 = 0
112 while k < L {
113 if col_present[k] == 1 {
114 var miss2: i64 = 0 - 1
115 var cnt2: i64 = 0
116 var jj: i64 = 0
117 while jj < D {
118 let idx: i64 = jj * L + k
119 if present[idx] == 0 { cnt2 = cnt2 + 1; miss2 = idx }
120 jj = jj + 1
121 }
122 if cnt2 == 1 {
123 let dst: *u8 = fec_pkt(src, miss2, S)
124 fec_zero(dst, S)
125 fec_xor_into(dst, fec_pkt(cols, k, S), S)
126 var jj2: i64 = 0
127 while jj2 < D {
128 let idx2: i64 = jj2 * L + k
129 if idx2 != miss2 { fec_xor_into(dst, fec_pkt(src, idx2, S), S) }
130 jj2 = jj2 + 1
131 }
132 present[miss2] = 1
133 progress = 1
134 }
135 }
136 k = k + 1
137 }
138 }
139 // remaining missing source packets
140 var rem: i64 = 0
141 var n: i64 = 0
142 let K: i64 = L * D
143 while n < K { if present[n] == 0 { rem = rem + 1 } n = n + 1 }
144 return rem
145}
146
147// ---- adaptive: size redundancy from the REAL per-flow loss estimate ----
148// (caller passes nx_linkqual lq_loss_pct). No loss -> no overhead; light
149// random loss -> row parity; heavy/bursty -> 2-D. Bootstrap thresholds
150// -> svc-config (CLAUDE.md 11).
151func fec_scheme_for_loss(loss_pct: i64) -> i64 {
152 if loss_pct <= 0 { return FEC_NONE }
153 if loss_pct < 5 { return FEC_ROW }
154 return FEC_2D
155}
156// overhead %% of a scheme for an LxD grid (repair pkts / source pkts * 100).
157func fec_overhead_pct(scheme: i64, L: i64, D: i64) -> i64 {
158 if scheme == FEC_NONE { return 0 }
159 let K: i64 = L * D
160 if K <= 0 { return 0 }
161 if scheme == FEC_ROW { return (D * 100) / K }
162 return ((D + L) * 100) / K
163}