1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//! Block interleaving: a sender-side transmit-order permutation that
//! converts a burst loss into a spread loss the per-block FEC can
//! recover.
//!
//! Without interleaving, the k+r datagrams of one block ship
//! consecutively, so a burst that drops B consecutive packets removes B
//! shards from one block - and if B > r, that block is unrecoverable.
//! With interleave depth D, the datagrams of D blocks ship column-major:
//! shard 0 of blocks 0..D, then shard 1 of blocks 0..D, and so on. The
//! datagrams of any one block are then spaced D apart on the wire, so a
//! burst of up to D consecutive losses removes at most ONE shard from
//! each block - back inside FEC's r-parity budget.
//!
//! This is purely a sender-side reordering. The receiver routes every
//! datagram by its `(block_id, shard_index)` header
//! ([`crate::reliable_udp::Decoder`]), so it reassembles correctly
//! regardless of arrival order and needs no de-interleave logic.
//!
//! Cost: the interleaver buffers up to D blocks before emitting the
//! first datagram, trading D-block latency for burst tolerance. D is a
//! control-table knob ([`crate::control_table::ControlTable`]); D = 1 is
//! pass-through with no added latency.
/// Buffers up to `depth` blocks of datagrams and emits them column-major
/// so each block's shards are spaced `depth` apart on the wire.
#[derive(Debug)]
pub struct Interleaver {
depth: usize,
/// Up to `depth` blocks, each a list of that block's datagrams.
pending: Vec<Vec<Vec<u8>>>,
}
impl Interleaver {
/// Create an interleaver of the given depth (clamped to at least 1).
pub fn new(depth: usize) -> Self {
Self {
depth: depth.max(1),
pending: Vec::new(),
}
}
/// Current interleave depth.
pub fn depth(&self) -> usize {
self.depth
}
/// Change the interleave depth. Any buffered blocks are flushed
/// first (returned to the caller) so the depth change does not
/// reorder a partially-staged group.
pub fn set_depth(&mut self, depth: usize) -> Vec<Vec<u8>> {
let flushed = self.flush();
self.depth = depth.max(1);
flushed
}
/// Number of blocks currently buffered.
pub fn buffered_blocks(&self) -> usize {
self.pending.len()
}
/// Stage one block's datagrams. Returns the interleaved datagrams
/// ready to transmit: empty until `depth` blocks are buffered, then
/// the whole interleaved group. At depth 1 the block is returned
/// immediately (pass-through, no added latency).
pub fn add_block(&mut self, datagrams: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
if datagrams.is_empty() {
return Vec::new();
}
self.pending.push(datagrams);
if self.pending.len() >= self.depth {
self.emit()
} else {
Vec::new()
}
}
/// Emit whatever blocks are buffered (a short final group),
/// interleaved. Empty if nothing is staged.
pub fn flush(&mut self) -> Vec<Vec<u8>> {
if self.pending.is_empty() {
Vec::new()
} else {
self.emit()
}
}
/// Column-major emit of the buffered blocks, then clear.
fn emit(&mut self) -> Vec<Vec<u8>> {
let mut blocks = std::mem::take(&mut self.pending);
let max_len = blocks.iter().map(|b| b.len()).max().unwrap_or(0);
let total: usize = blocks.iter().map(|b| b.len()).sum();
let mut out = Vec::with_capacity(total);
// For each shard column, emit that column's datagram from every block
// that has one. Block i's shards land at output positions separated by
// the number of blocks, so a burst of <= depth consecutive losses hits
// at most one shard per block. The datagram is MOVED out, not cloned:
// `blocks` is owned here and dropped on return, so swapping in an empty
// Vec transfers ownership with no per-datagram allocation or byte copy.
for col in 0..max_len {
for block in blocks.iter_mut() {
if col < block.len() {
out.push(std::mem::take(&mut block[col]));
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Build `n` "blocks" of `shards` tiny datagrams each; each datagram
/// encodes (block_index, shard_index) in its first two bytes so the
/// permutation and burst properties are checkable.
fn make_blocks(n: usize, shards: usize) -> Vec<Vec<Vec<u8>>> {
(0..n)
.map(|b| (0..shards).map(|s| vec![b as u8, s as u8]).collect())
.collect()
}
#[test]
fn depth_one_is_passthrough() {
let mut il = Interleaver::new(1);
let blocks = make_blocks(1, 5);
let out = il.add_block(blocks[0].clone());
assert_eq!(out, blocks[0], "depth 1 emits the block immediately");
assert_eq!(il.buffered_blocks(), 0);
}
#[test]
fn emits_when_depth_reached_and_is_a_permutation() {
let depth = 4;
let shards = 6;
let blocks = make_blocks(depth, shards);
let mut il = Interleaver::new(depth);
let mut out = Vec::new();
for (i, b) in blocks.iter().enumerate() {
let emitted = il.add_block(b.clone());
if i < depth - 1 {
assert!(emitted.is_empty(), "no emit before depth reached");
} else {
out = emitted;
}
}
// Output is a permutation of all input datagrams (no loss/dup).
let mut got = out.clone();
let mut want: Vec<Vec<u8>> = blocks.into_iter().flatten().collect();
got.sort();
want.sort();
assert_eq!(got, want, "interleave is a permutation of the input");
}
/// The core property: with depth D, any window of D consecutive
/// emitted datagrams contains at most ONE shard from any block.
fn burst_property(depth: usize, shards: usize) {
let blocks = make_blocks(depth, shards);
let mut il = Interleaver::new(depth);
let mut out = Vec::new();
for b in &blocks {
out.extend(il.add_block(b.clone()));
}
out.extend(il.flush());
assert_eq!(out.len(), depth * shards);
// Slide a window of `depth` and count per-block hits.
for start in 0..=out.len() - depth {
let mut per_block = vec![0u32; depth];
for pkt in &out[start..start + depth] {
per_block[pkt[0] as usize] += 1;
}
assert!(
per_block.iter().all(|&c| c <= 1),
"depth={depth} shards={shards} window@{start}: a block lost >1 shard to a burst of {depth}"
);
}
}
#[test]
fn burst_of_depth_hits_at_most_one_shard_per_block() {
burst_property(4, 6);
burst_property(8, 10);
burst_property(3, 3);
burst_property(16, 8);
}
#[test]
fn set_depth_flushes_pending() {
let mut il = Interleaver::new(4);
let blocks = make_blocks(2, 5); // fewer than depth
il.add_block(blocks[0].clone());
let flushed = il.add_block(blocks[1].clone());
assert!(flushed.is_empty(), "2 of 4 staged, nothing emitted yet");
let out = il.set_depth(2);
assert_eq!(out.len(), 10, "changing depth flushes the 2 staged blocks");
assert_eq!(il.depth(), 2);
assert_eq!(il.buffered_blocks(), 0);
}
}
/// Gilbert-Elliott burst-loss A/B: interleaving must cut the ARQ load it
/// would otherwise take to recover bursts, by spreading each burst so the
/// per-block FEC absorbs it. Both depths deliver exactly (ARQ is the
/// floor); the metric is how many retransmits each needed.
#[cfg(test)]
mod gilbert_elliott {
use super::Interleaver;
use crate::reliable_udp::{Decoder, Encoder};
/// Two-state burst channel. Probabilities are per-1000.
struct Ge {
bad: bool,
rng: u64,
p_gb: u32, // good -> bad
p_bg: u32, // bad -> good (mean burst length = 1000 / p_bg)
p_b: u32, // loss while bad
p_g: u32, // loss while good
}
impl Ge {
fn new(seed: u64) -> Self {
Self { bad: false, rng: seed | 1, p_gb: 30, p_bg: 200, p_b: 900, p_g: 0 }
}
fn rand(&mut self) -> u32 {
self.rng = self
.rng
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(self.rng >> 33) as u32
}
/// Advance the state, then decide loss for one datagram.
fn drop(&mut self) -> bool {
if self.bad {
if self.rand() % 1000 < self.p_bg {
self.bad = false;
}
} else if self.rand() % 1000 < self.p_gb {
self.bad = true;
}
let p = if self.bad { self.p_b } else { self.p_g };
self.rand() % 1000 < p
}
}
/// Encode `n` items at the given interleave depth, push the
/// interleaved stream through the GE channel, and drive ARQ (also
/// lossy) to completion. Returns (delivered-exactly, retransmits).
fn run(depth: usize, n: u64, seed: u64) -> (bool, usize) {
let (k, r) = (8usize, 2usize);
let mut enc = Encoder::new(k, r, 8);
let mut il = Interleaver::new(depth);
let mut dec = Decoder::new();
// Build the interleaved wire order (all blocks sealed before any
// feedback, so parity r stays constant at 2 throughout).
let mut wire: Vec<Vec<u8>> = Vec::new();
for i in 0..n {
let block = enc.push(&i.to_le_bytes());
if !block.is_empty() {
wire.extend(il.add_block(block));
}
}
let tail = enc.flush();
if !tail.is_empty() {
wire.extend(il.add_block(tail));
}
wire.extend(il.flush());
let mut ge = Ge::new(seed);
let mut delivered: Vec<u64> = Vec::new();
for pkt in &wire {
if ge.drop() {
continue;
}
for it in dec.on_packet(pkt) {
delivered.push(u64::from_le_bytes(it.try_into().unwrap()));
}
}
// ARQ loop: retransmits also traverse the GE channel.
let mut retransmits = 0usize;
let mut rounds = 0u32;
while (delivered.len() as u64) < n {
rounds += 1;
assert!(rounds < 20_000, "no convergence at depth {depth}");
let fb = dec.feedback(true);
for pkt in enc.on_feedback(&fb) {
retransmits += 1;
if ge.drop() {
continue;
}
for it in dec.on_packet(&pkt) {
delivered.push(u64::from_le_bytes(it.try_into().unwrap()));
}
}
}
let ok = delivered == (0..n).collect::<Vec<_>>();
(ok, retransmits)
}
#[test]
fn interleaving_cuts_arq_under_bursty_loss() {
let n = 240;
let seed = 0x00C0_FFEE_1234_5678;
let (ok1, rtx1) = run(1, n, seed);
let (ok8, rtx8) = run(8, n, seed);
assert!(ok1 && ok8, "both deliver exactly via the ARQ floor");
assert!(
rtx8 < rtx1,
"interleaving must cut ARQ under bursts: depth8={rtx8} retransmits vs depth1={rtx1}"
);
}
}