1use std::collections::VecDeque;
29use std::io;
30use std::net::SocketAddr;
31use std::time::Duration;
32
33use crate::schema_codec::SchemaTemplate;
34use crate::udp_bridge::{ReliableUdpReceiver, ReliableUdpSender};
35
36const TAG_DATA: u8 = 0;
39const TAG_TEMPLATE: u8 = 1;
40const TAG_BATCH: u8 = 2;
44
45pub struct CompressedSender {
48 inner: ReliableUdpSender,
49 tpl: SchemaTemplate,
50 item: Vec<u8>,
51 started: bool,
52 relearn: bool,
53 window: usize,
54 threshold_pct: u32,
55 recent: VecDeque<Vec<u8>>,
56 escapes: usize,
57 since_check: usize,
58 relearns: usize,
59 coalesce: bool,
60 batch_target: usize,
61 batch: Vec<u8>,
62 cslot: Vec<u8>,
63}
64
65impl CompressedSender {
66 pub fn new(inner: ReliableUdpSender, tpl: SchemaTemplate) -> Self {
68 let cap = tpl.width() + 16;
69 Self {
70 inner,
71 tpl,
72 item: Vec::with_capacity(cap),
73 started: false,
74 relearn: false,
75 window: 0,
76 threshold_pct: 0,
77 recent: VecDeque::new(),
78 escapes: 0,
79 since_check: 0,
80 relearns: 0,
81 coalesce: false,
82 batch_target: 0,
83 batch: Vec::new(),
84 cslot: Vec::with_capacity(cap),
85 }
86 }
87
88 pub fn relearns(&self) -> usize {
91 self.relearns
92 }
93
94 pub fn with_coalesce(mut self, target: usize) -> Self {
101 self.coalesce = true;
102 self.batch_target = target.max(8);
103 self.batch = Vec::with_capacity(self.batch_target + 8);
104 self.batch.push(TAG_BATCH);
105 self
106 }
107
108 fn flush_batch(&mut self) -> io::Result<()> {
110 if self.batch.len() > 1 {
111 self.inner.send_item(&self.batch)?;
112 self.batch.clear();
113 self.batch.push(TAG_BATCH);
114 }
115 Ok(())
116 }
117
118 pub fn bind(
122 peer: SocketAddr,
123 k: usize,
124 r: usize,
125 max_item: usize,
126 tpl: SchemaTemplate,
127 ) -> io::Result<Self> {
128 let inner = ReliableUdpSender::bind("0.0.0.0:0", peer, k, r, max_item)?;
129 Ok(Self::new(inner, tpl))
130 }
131
132 pub fn with_relearn(mut self, window: usize, threshold_pct: u32) -> Self {
136 self.relearn = true;
137 self.window = window.max(64);
138 self.threshold_pct = threshold_pct.min(100);
139 self.recent = VecDeque::with_capacity(self.window);
140 self
141 }
142
143 fn send_template(&mut self) -> io::Result<()> {
144 self.item.clear();
145 self.item.push(TAG_TEMPLATE);
146 self.item.extend_from_slice(&self.tpl.serialize());
147 self.inner.send_item(&self.item)
148 }
149
150 fn relearn_now(&mut self) -> io::Result<()> {
151 let width = self.tpl.width();
152 let sample: Vec<&[u8]> = self.recent.iter().map(|s| s.as_slice()).collect();
153 self.tpl = SchemaTemplate::learn(&sample, width);
154 self.relearns += 1;
155 self.send_template()
156 }
157
158 pub fn send_item(&mut self, slot: &[u8]) -> io::Result<()> {
162 if !self.started {
163 self.send_template()?;
164 self.started = true;
165 }
166 if self.relearn {
167 if self.recent.len() == self.window {
168 self.recent.pop_front();
169 }
170 self.recent.push_back(slot.to_vec());
171 }
172 self.cslot.clear();
173 self.tpl.encode(slot, &mut self.cslot);
174 let escaped = SchemaTemplate::is_escape(&self.cslot);
175
176 if self.coalesce {
177 if self.batch.len() + 2 + self.cslot.len() > self.batch_target && self.batch.len() > 1 {
180 self.flush_batch()?;
181 }
182 self.batch
183 .extend_from_slice(&(self.cslot.len() as u16).to_le_bytes());
184 self.batch.extend_from_slice(&self.cslot);
185 } else {
186 self.item.clear();
187 self.item.push(TAG_DATA);
188 self.item.extend_from_slice(&self.cslot);
189 self.inner.send_item(&self.item)?;
190 }
191
192 if self.relearn {
193 if escaped {
194 self.escapes += 1;
195 }
196 self.since_check += 1;
197 if self.since_check >= self.window {
198 if self.escapes * 100 > self.threshold_pct as usize * self.window {
199 self.flush_batch()?;
202 self.relearn_now()?;
203 }
204 self.escapes = 0;
205 self.since_check = 0;
206 }
207 }
208 Ok(())
209 }
210
211 pub fn flow_blocked(&self) -> bool {
214 self.inner.flow_blocked()
215 }
216
217 pub fn pump_feedback(&mut self) -> io::Result<()> {
219 self.inner.pump_feedback()
220 }
221
222 pub fn flush(&mut self) -> io::Result<()> {
224 self.flush_batch()?;
225 self.inner.flush()
226 }
227
228 pub fn drain_until_acked(&mut self, timeout: Duration) -> io::Result<bool> {
230 self.inner.drain_until_acked(timeout)
231 }
232}
233
234pub struct CompressedReceiver {
237 inner: ReliableUdpReceiver,
238 tpl: Option<SchemaTemplate>,
239}
240
241impl CompressedReceiver {
242 pub fn new(inner: ReliableUdpReceiver) -> Self {
244 Self { inner, tpl: None }
245 }
246
247 pub fn bind(local: SocketAddr) -> io::Result<Self> {
249 Ok(Self::new(ReliableUdpReceiver::bind(local)?))
250 }
251
252 pub fn poll(&mut self) -> io::Result<Vec<Vec<u8>>> {
256 let raw = self.inner.poll()?;
257 let mut out = Vec::with_capacity(raw.len());
258 for item in raw {
259 if item.is_empty() {
260 continue;
261 }
262 let (tag, payload) = (item[0], &item[1..]);
263 if tag == TAG_TEMPLATE {
264 if let Some(t) = SchemaTemplate::deserialize(payload) {
267 self.tpl = Some(t);
268 }
269 } else if tag == TAG_BATCH {
270 if let Some(t) = &self.tpl {
272 let mut off = 0usize;
273 while off + 2 <= payload.len() {
274 let len = u16::from_le_bytes([payload[off], payload[off + 1]]) as usize;
275 off += 2;
276 if off + len > payload.len() {
277 break;
278 }
279 let mut slot = vec![0u8; t.width()];
280 t.decode(&payload[off..off + len], &mut slot);
281 out.push(slot);
282 off += len;
283 }
284 }
285 } else if let Some(t) = &self.tpl {
286 let mut slot = vec![0u8; t.width()];
288 t.decode(payload, &mut slot);
289 out.push(slot);
290 }
291 }
292 Ok(out)
293 }
294
295 pub fn nudge_feedback(&mut self) -> io::Result<()> {
298 self.inner.nudge_feedback()
299 }
300
301 pub fn with_debug_loss(mut self, pct: u32, seed: u64) -> Self {
303 self.inner = self.inner.with_debug_loss(pct, seed);
304 self
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::shared_deque_khpd::{FatLineItem, LineItem};
312 use std::net::{IpAddr, Ipv4Addr};
313 use std::thread;
314 use subetha_core::Marshal;
315
316 struct Rng(u64);
317 impl Rng {
318 fn new(s: u64) -> Self {
319 Self(s | 1)
320 }
321 fn next(&mut self) -> u64 {
322 let mut x = self.0;
323 x ^= x << 13;
324 x ^= x >> 7;
325 x ^= x << 17;
326 self.0 = x;
327 x
328 }
329 fn byte(&mut self) -> u8 {
330 (self.next() >> 24) as u8
331 }
332 fn below(&mut self, n: u64) -> u64 {
333 self.next() % n
334 }
335 }
336
337 fn slots(n: usize, seed: u64, phase: u8) -> Vec<[u8; 64]> {
340 let mut rng = Rng::new(seed);
341 let mut out = Vec::with_capacity(n);
342 let mut id = 0u32;
343 for _ in 0..n {
344 let cnt = 1 + rng.below(3) as usize;
345 let mut items = Vec::with_capacity(cnt);
346 for _ in 0..cnt {
347 let mut b = [0u8; 16];
348 b[0] = rng.below(16) as u8;
349 b[1] = phase; b[4..8].copy_from_slice(&id.to_le_bytes());
351 id = id.wrapping_add(1);
352 for x in b.iter_mut().skip(8) {
353 *x = rng.byte();
354 }
355 items.push(LineItem::new(&b).unwrap());
356 }
357 let fat = FatLineItem::from_items(&items).unwrap();
358 let mut s = [0u8; 64];
359 fat.marshal(&mut s);
360 out.push(s);
361 }
362 out
363 }
364
365 fn run_at_loss(loss: u32, port: u16) {
366 let total = 4000usize;
367 let data = slots(total, 0x99, 0);
368 let sample: Vec<&[u8]> = data.iter().step_by(7).map(|s| s.as_slice()).collect();
369 let tpl = SchemaTemplate::learn(&sample, 64);
370 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
371
372 let mut recv = CompressedReceiver::bind(addr).unwrap();
373 if loss > 0 {
374 recv = recv.with_debug_loss(loss, 0x1234);
375 }
376 let expect = data.clone();
377 let rx = thread::spawn(move || -> bool {
378 let mut got: Vec<Vec<u8>> = Vec::with_capacity(total);
379 let start = std::time::Instant::now();
380 while got.len() < total {
381 if start.elapsed() > Duration::from_secs(60) {
382 return false;
383 }
384 for s in recv.poll().unwrap_or_default() {
385 got.push(s);
386 }
387 }
388 for _ in 0..50 {
389 recv.nudge_feedback().ok();
390 thread::sleep(Duration::from_millis(2));
391 }
392 got.len() == total && got.iter().zip(expect.iter()).all(|(a, b)| a.as_slice() == &b[..])
393 });
394
395 thread::sleep(Duration::from_millis(150));
396 let mut send = CompressedSender::bind(addr, 8, 2, 256, tpl).unwrap();
397 for s in &data {
398 while send.flow_blocked() {
399 send.pump_feedback().ok();
400 if send.flow_blocked() {
401 thread::sleep(Duration::from_micros(50));
402 }
403 }
404 send.send_item(s).unwrap();
405 }
406 send.flush().unwrap();
407 let acked = send.drain_until_acked(Duration::from_secs(60)).unwrap();
408 let ok = rx.join().unwrap();
409 assert!(ok, "byte-exact delivery failed at {loss}% loss");
410 assert!(acked, "not fully acked at {loss}% loss");
411 }
412
413 #[test]
414 fn compressed_exact_clean() {
415 run_at_loss(0, 25410);
416 }
417
418 #[test]
419 fn compressed_exact_15pct_loss() {
420 run_at_loss(15, 25411);
421 }
422
423 #[test]
424 fn compressed_exact_30pct_loss() {
425 run_at_loss(30, 25412);
426 }
427
428 #[test]
432 fn relearn_keeps_exact_across_schema_drift() {
433 let half = 3000usize;
434 let mut data = slots(half, 0x21, 0);
435 data.extend(slots(half, 0x22, 7)); let total = data.len();
437 let sample: Vec<&[u8]> = data[..half].iter().step_by(5).map(|s| s.as_slice()).collect();
438 let tpl = SchemaTemplate::learn(&sample, 64);
439 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 25420);
440
441 let mut recv = CompressedReceiver::bind(addr).unwrap();
442 let expect = data.clone();
443 let rx = thread::spawn(move || -> bool {
444 let mut got: Vec<Vec<u8>> = Vec::with_capacity(total);
445 let start = std::time::Instant::now();
446 while got.len() < total {
447 if start.elapsed() > Duration::from_secs(60) {
448 return false;
449 }
450 for s in recv.poll().unwrap_or_default() {
451 got.push(s);
452 }
453 }
454 let ok = got.len() == total
455 && got.iter().zip(expect.iter()).all(|(a, b)| a.as_slice() == &b[..]);
456 for _ in 0..50 {
458 recv.nudge_feedback().ok();
459 thread::sleep(Duration::from_millis(2));
460 }
461 ok
462 });
463
464 thread::sleep(Duration::from_millis(150));
465 let mut send = CompressedSender::bind(addr, 8, 2, 256, tpl)
467 .unwrap()
468 .with_relearn(256, 20);
469 for s in &data {
470 while send.flow_blocked() {
471 send.pump_feedback().ok();
472 if send.flow_blocked() {
473 thread::sleep(Duration::from_micros(50));
474 }
475 }
476 send.send_item(s).unwrap();
477 }
478 send.flush().unwrap();
479 let acked = send.drain_until_acked(Duration::from_secs(60)).unwrap();
480 assert!(rx.join().unwrap(), "byte-exact across schema drift failed");
481 assert!(acked, "not fully acked");
482 }
483
484 #[test]
487 fn coalesce_exact_with_loss() {
488 let total = 6000usize;
489 let data = slots(total, 0x77, 0);
490 let sample: Vec<&[u8]> = data.iter().step_by(7).map(|s| s.as_slice()).collect();
491 let tpl = SchemaTemplate::learn(&sample, 64);
492 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 25430);
493
494 let mut recv = CompressedReceiver::bind(addr).unwrap().with_debug_loss(15, 0x55);
495 let expect = data.clone();
496 let rx = thread::spawn(move || -> bool {
497 let mut got: Vec<Vec<u8>> = Vec::with_capacity(total);
498 let start = std::time::Instant::now();
499 while got.len() < total {
500 if start.elapsed() > Duration::from_secs(60) {
501 return false;
502 }
503 for s in recv.poll().unwrap_or_default() {
504 got.push(s);
505 }
506 }
507 let ok = got.len() == total
508 && got.iter().zip(expect.iter()).all(|(a, b)| a.as_slice() == &b[..]);
509 for _ in 0..50 {
510 recv.nudge_feedback().ok();
511 thread::sleep(Duration::from_millis(2));
512 }
513 ok
514 });
515
516 thread::sleep(Duration::from_millis(150));
517 let mut send = CompressedSender::bind(addr, 8, 2, 1400, tpl)
518 .unwrap()
519 .with_coalesce(1200);
520 for s in &data {
521 while send.flow_blocked() {
522 send.pump_feedback().ok();
523 if send.flow_blocked() {
524 thread::sleep(Duration::from_micros(50));
525 }
526 }
527 send.send_item(s).unwrap();
528 }
529 send.flush().unwrap();
530 let acked = send.drain_until_acked(Duration::from_secs(60)).unwrap();
531 assert!(rx.join().unwrap(), "coalesced byte-exact failed");
532 assert!(acked, "coalesced not fully acked");
533 }
534}