1pub const PKT_CONTROL: u8 = 4;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36pub enum FrameType {
37 Ack = 0x01,
39 Nak = 0x02,
41 Loss = 0x03,
43 Timing = 0x04,
45 Ring = 0x05,
47 Path = 0x06,
49 Link = 0x07,
51 LossAcct = 0x08,
54 Pmtu = 0x09,
56 BwProbe = 0x0A,
58 Trace = 0x0B,
60 AvailBw = 0x0C,
63 Forecast = 0x0D,
66 Periodicity = 0x0E,
69 SessionChallenge = 0x0F,
72 SessionResponse = 0x10,
74 SessionAnnounce = 0x11,
78}
79
80impl FrameType {
81 fn from_u8(v: u8) -> Option<Self> {
84 Some(match v {
85 0x01 => Self::Ack,
86 0x02 => Self::Nak,
87 0x03 => Self::Loss,
88 0x04 => Self::Timing,
89 0x05 => Self::Ring,
90 0x06 => Self::Path,
91 0x07 => Self::Link,
92 0x08 => Self::LossAcct,
93 0x09 => Self::Pmtu,
94 0x0A => Self::BwProbe,
95 0x0B => Self::Trace,
96 0x0C => Self::AvailBw,
97 0x0D => Self::Forecast,
98 0x0E => Self::Periodicity,
99 0x0F => Self::SessionChallenge,
100 0x10 => Self::SessionResponse,
101 0x11 => Self::SessionAnnounce,
102 _ => return None,
103 })
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
114pub struct SessionFrame {
115 pub epoch: u32,
116 pub nonce: u64,
117}
118
119pub const NONCE_MASK: u64 = (1u64 << 62) - 1;
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub struct AckFrame {
125 pub ack_through: u32,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
130pub struct NakFrame {
131 pub block: u32,
132 pub mask: u32,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
137pub struct LossFrame {
138 pub loss_x255: u8,
139 pub burstiness_x255: u8,
140 pub owd_trend_class: u8,
141 pub loss_class: u8,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
150pub struct TimingFrame {
151 pub send_ts: u64,
152 pub echo_ts: u64,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
157pub struct RingFrame {
158 pub fill_pct: u8,
159 pub ring_kind: u8,
160 pub producers: u8,
161 pub consumers: u8,
162 pub trend: u8,
163 pub flags: u8,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175pub struct PathFrame {
176 pub ttl: u8,
177 pub ecn: u8,
178 pub hop_count: u8,
179 pub ce_count: u64,
180 pub ect_count: u64,
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
186pub struct LinkFrame {
187 pub class: u8,
188 pub quality: u8,
189}
190
191pub mod link_class {
193 pub const UNKNOWN: u8 = 0;
194 pub const LOOPBACK: u8 = 1;
195 pub const WIRED: u8 = 2;
196 pub const WIFI: u8 = 3;
197 pub const CELLULAR: u8 = 4;
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
207pub struct LossAcctFrame {
208 pub seq: u32,
209 pub last_recv_seq: u32,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
215pub struct PmtuFrame {
216 pub pmtu: u16,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
222pub struct BwProbeFrame {
223 pub probe_id: u8,
224 pub idx: u8,
225 pub send_ts: u64,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
232pub struct TraceFrame {
233 pub hop_ttl: u8,
234 pub probe_id: u8,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
241pub struct AvailBwFrame {
242 pub avail_kbps: u64,
244 pub capacity_kbps: u64,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
253pub struct ForecastFrame {
254 pub forecast_kbps: u64,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
263pub struct PeriodicityFrame {
264 pub period_ds: u64,
265 pub secs_to_spike_ds: u64,
266 pub confidence_x255: u8,
267}
268
269#[derive(Debug, Clone, Default, PartialEq, Eq)]
273pub struct ControlPacket {
274 pub ack: Option<AckFrame>,
275 pub nak: Option<NakFrame>,
276 pub loss: Option<LossFrame>,
277 pub timing: Option<TimingFrame>,
278 pub ring: Option<RingFrame>,
279 pub path: Option<PathFrame>,
280 pub link: Option<LinkFrame>,
281 pub loss_acct: Option<LossAcctFrame>,
282 pub pmtu: Option<PmtuFrame>,
283 pub bw_probe: Vec<BwProbeFrame>,
284 pub trace: Vec<TraceFrame>,
285 pub avail_bw: Option<AvailBwFrame>,
286 pub forecast: Option<ForecastFrame>,
287 pub periodicity: Option<PeriodicityFrame>,
288 pub session_challenge: Option<SessionFrame>,
289 pub session_response: Option<SessionFrame>,
290 pub session_announce: Option<u32>,
292}
293
294impl ControlPacket {
295 pub fn new() -> Self {
297 Self::default()
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.ack.is_none()
303 && self.nak.is_none()
304 && self.loss.is_none()
305 && self.timing.is_none()
306 && self.ring.is_none()
307 && self.path.is_none()
308 && self.link.is_none()
309 && self.loss_acct.is_none()
310 && self.pmtu.is_none()
311 && self.bw_probe.is_empty()
312 && self.trace.is_empty()
313 }
314}
315
316pub fn is_control(buf: &[u8]) -> bool {
318 !buf.is_empty() && buf[0] == PKT_CONTROL
319}
320
321fn put_varint(out: &mut Vec<u8>, v: u64) {
327 const MAX62: u64 = (1 << 62) - 1;
328 let v = v.min(MAX62);
329 if v < (1 << 6) {
330 out.push(v as u8);
331 } else if v < (1 << 14) {
332 out.push(0x40 | (v >> 8) as u8);
333 out.push(v as u8);
334 } else if v < (1 << 30) {
335 out.push(0x80 | (v >> 24) as u8);
336 out.extend_from_slice(&(v as u32).to_be_bytes()[1..]);
337 } else {
338 out.push(0xC0 | (v >> 56) as u8);
339 out.extend_from_slice(&v.to_be_bytes()[1..]);
340 }
341}
342
343fn get_varint(buf: &[u8], pos: usize) -> Option<(u64, usize)> {
346 let first = *buf.get(pos)?;
347 let len = 1usize << (first >> 6);
348 if pos + len > buf.len() {
349 return None;
350 }
351 let mut v = (first & 0x3F) as u64;
352 for &b in &buf[pos + 1..pos + len] {
353 v = (v << 8) | b as u64;
354 }
355 Some((v, pos + len))
356}
357
358fn put_frame(out: &mut Vec<u8>, ty: FrameType, body: &[u8]) {
362 out.push(ty as u8);
363 put_varint(out, body.len() as u64);
364 out.extend_from_slice(body);
365}
366
367pub fn pad_control_to(buf: &mut Vec<u8>, target_len: usize) {
376 const PAD_TYPE: u8 = 0x7F;
377 const HEADER: usize = 3; if target_len < buf.len() + HEADER + 64 {
379 return;
380 }
381 let body_len = target_len - buf.len() - HEADER;
382 buf.push(PAD_TYPE);
383 put_varint(buf, body_len as u64);
384 buf.resize(buf.len() + body_len, 0);
385}
386
387pub fn encode_control(p: &ControlPacket) -> Vec<u8> {
389 let mut out = Vec::with_capacity(64);
390 out.push(PKT_CONTROL);
391 let mut body = Vec::with_capacity(16);
392
393 if let Some(f) = p.ack {
394 body.clear();
395 put_varint(&mut body, f.ack_through as u64);
396 put_frame(&mut out, FrameType::Ack, &body);
397 }
398 if let Some(f) = p.nak {
399 body.clear();
400 put_varint(&mut body, f.block as u64);
401 put_varint(&mut body, f.mask as u64);
402 put_frame(&mut out, FrameType::Nak, &body);
403 }
404 if let Some(f) = p.loss {
405 put_frame(
406 &mut out,
407 FrameType::Loss,
408 &[f.loss_x255, f.burstiness_x255, f.owd_trend_class, f.loss_class],
409 );
410 }
411 if let Some(f) = p.timing {
412 body.clear();
413 put_varint(&mut body, f.send_ts);
414 put_varint(&mut body, f.echo_ts);
415 put_frame(&mut out, FrameType::Timing, &body);
416 }
417 if let Some(f) = p.ring {
418 put_frame(
419 &mut out,
420 FrameType::Ring,
421 &[
422 f.fill_pct,
423 f.ring_kind,
424 f.producers,
425 f.consumers,
426 f.trend,
427 f.flags,
428 ],
429 );
430 }
431 if let Some(f) = p.path {
432 body.clear();
433 body.extend_from_slice(&[f.ttl, f.ecn, f.hop_count]);
434 put_varint(&mut body, f.ce_count);
435 put_varint(&mut body, f.ect_count);
436 put_frame(&mut out, FrameType::Path, &body);
437 }
438 if let Some(f) = p.link {
439 put_frame(&mut out, FrameType::Link, &[f.class, f.quality]);
440 }
441 if let Some(f) = p.loss_acct {
442 body.clear();
443 put_varint(&mut body, f.seq as u64);
444 put_varint(&mut body, f.last_recv_seq as u64);
445 put_frame(&mut out, FrameType::LossAcct, &body);
446 }
447 if let Some(f) = p.pmtu {
448 body.clear();
449 put_varint(&mut body, f.pmtu as u64);
450 put_frame(&mut out, FrameType::Pmtu, &body);
451 }
452 for f in &p.bw_probe {
453 body.clear();
454 body.push(f.probe_id);
455 body.push(f.idx);
456 put_varint(&mut body, f.send_ts);
457 put_frame(&mut out, FrameType::BwProbe, &body);
458 }
459 for f in &p.trace {
460 put_frame(&mut out, FrameType::Trace, &[f.hop_ttl, f.probe_id]);
461 }
462 if let Some(f) = p.avail_bw {
463 body.clear();
464 put_varint(&mut body, f.avail_kbps);
465 put_varint(&mut body, f.capacity_kbps);
466 put_frame(&mut out, FrameType::AvailBw, &body);
467 }
468 if let Some(f) = p.forecast {
469 body.clear();
470 put_varint(&mut body, f.forecast_kbps);
471 put_frame(&mut out, FrameType::Forecast, &body);
472 }
473 if let Some(f) = p.periodicity {
474 body.clear();
475 put_varint(&mut body, f.period_ds);
476 put_varint(&mut body, f.secs_to_spike_ds);
477 body.push(f.confidence_x255);
478 put_frame(&mut out, FrameType::Periodicity, &body);
479 }
480 if let Some(f) = p.session_challenge {
481 body.clear();
482 put_varint(&mut body, u64::from(f.epoch));
483 put_varint(&mut body, f.nonce);
484 put_frame(&mut out, FrameType::SessionChallenge, &body);
485 }
486 if let Some(f) = p.session_response {
487 body.clear();
488 put_varint(&mut body, u64::from(f.epoch));
489 put_varint(&mut body, f.nonce);
490 put_frame(&mut out, FrameType::SessionResponse, &body);
491 }
492 if let Some(epoch) = p.session_announce {
493 body.clear();
494 put_varint(&mut body, u64::from(epoch));
495 put_frame(&mut out, FrameType::SessionAnnounce, &body);
496 }
497 out
498}
499
500pub fn decode_control(buf: &[u8]) -> Option<ControlPacket> {
505 if !is_control(buf) {
506 return None;
507 }
508 let mut p = ControlPacket::new();
509 let mut pos = 1usize;
510 while pos < buf.len() {
511 let ty = buf[pos];
512 pos += 1;
513 let (len, next) = match get_varint(buf, pos) {
514 Some(v) => v,
515 None => break,
516 };
517 pos = next;
518 let end = pos + len as usize;
519 if end > buf.len() {
520 break;
521 }
522 let body = &buf[pos..end];
523 match FrameType::from_u8(ty) {
524 Some(FrameType::Ack) => {
525 if let Some((v, _)) = get_varint(body, 0) {
526 p.ack = Some(AckFrame {
527 ack_through: v as u32,
528 });
529 }
530 }
531 Some(FrameType::Nak) => {
532 if let Some((block, q)) = get_varint(body, 0)
533 && let Some((mask, _)) = get_varint(body, q)
534 {
535 p.nak = Some(NakFrame {
536 block: block as u32,
537 mask: mask as u32,
538 });
539 }
540 }
541 Some(FrameType::Loss) if body.len() >= 3 => {
542 p.loss = Some(LossFrame {
543 loss_x255: body[0],
544 burstiness_x255: body[1],
545 owd_trend_class: body[2],
546 loss_class: body.get(3).copied().unwrap_or(0),
549 });
550 }
551 Some(FrameType::Timing) => {
552 if let Some((send_ts, q)) = get_varint(body, 0)
553 && let Some((echo_ts, _)) = get_varint(body, q)
554 {
555 p.timing = Some(TimingFrame { send_ts, echo_ts });
556 }
557 }
558 Some(FrameType::Ring) if body.len() >= 6 => {
559 p.ring = Some(RingFrame {
560 fill_pct: body[0],
561 ring_kind: body[1],
562 producers: body[2],
563 consumers: body[3],
564 trend: body[4],
565 flags: body[5],
566 });
567 }
568 Some(FrameType::Path) if body.len() >= 3 => {
569 let (ce_count, n1) = get_varint(body, 3).unwrap_or((0, 3));
572 let (ect_count, _) = get_varint(body, n1).unwrap_or((0, n1));
573 p.path = Some(PathFrame {
574 ttl: body[0],
575 ecn: body[1],
576 hop_count: body[2],
577 ce_count,
578 ect_count,
579 });
580 }
581 Some(FrameType::Link) if body.len() >= 2 => {
582 p.link = Some(LinkFrame {
583 class: body[0],
584 quality: body[1],
585 });
586 }
587 Some(FrameType::LossAcct) => {
588 if let Some((seq, n)) = get_varint(body, 0)
589 && let Some((lrs, _)) = get_varint(body, n)
590 {
591 p.loss_acct = Some(LossAcctFrame {
592 seq: seq as u32,
593 last_recv_seq: lrs as u32,
594 });
595 }
596 }
597 Some(FrameType::Pmtu) => {
598 if let Some((v, _)) = get_varint(body, 0) {
599 p.pmtu = Some(PmtuFrame { pmtu: v as u16 });
600 }
601 }
602 Some(FrameType::BwProbe) if body.len() >= 2 => {
603 if let Some((send_ts, _)) = get_varint(body, 2) {
604 p.bw_probe.push(BwProbeFrame {
605 probe_id: body[0],
606 idx: body[1],
607 send_ts,
608 });
609 }
610 }
611 Some(FrameType::Trace) if body.len() >= 2 => {
612 p.trace.push(TraceFrame {
613 hop_ttl: body[0],
614 probe_id: body[1],
615 });
616 }
617 Some(FrameType::AvailBw) => {
618 if let Some((avail, n)) = get_varint(body, 0)
619 && let Some((cap, _)) = get_varint(body, n)
620 {
621 p.avail_bw = Some(AvailBwFrame {
622 avail_kbps: avail,
623 capacity_kbps: cap,
624 });
625 }
626 }
627 Some(FrameType::SessionAnnounce) => {
628 if let Some((epoch, _)) = get_varint(body, 0) {
629 p.session_announce = Some(epoch as u32);
630 }
631 }
632 Some(FrameType::SessionChallenge) => {
633 if let Some((epoch, n)) = get_varint(body, 0)
634 && let Some((nonce, _)) = get_varint(body, n)
635 {
636 p.session_challenge = Some(SessionFrame { epoch: epoch as u32, nonce });
637 }
638 }
639 Some(FrameType::SessionResponse) => {
640 if let Some((epoch, n)) = get_varint(body, 0)
641 && let Some((nonce, _)) = get_varint(body, n)
642 {
643 p.session_response = Some(SessionFrame { epoch: epoch as u32, nonce });
644 }
645 }
646 Some(FrameType::Forecast) => {
647 if let Some((fc, _)) = get_varint(body, 0) {
648 p.forecast = Some(ForecastFrame { forecast_kbps: fc });
649 }
650 }
651 Some(FrameType::Periodicity) => {
652 if let Some((period, n1)) = get_varint(body, 0)
653 && let Some((to_spike, n2)) = get_varint(body, n1)
654 && n2 < body.len()
655 {
656 p.periodicity = Some(PeriodicityFrame {
657 period_ds: period,
658 secs_to_spike_ds: to_spike,
659 confidence_x255: body[n2],
660 });
661 }
662 }
663 _ => {}
665 }
666 pos = end;
667 }
668 Some(p)
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
676 fn varint_round_trips_each_length_class() {
677 for v in [0u64, 1, 63, 64, 16383, 16384, (1 << 30) - 1, 1 << 30, (1u64 << 62) - 1] {
678 let mut b = Vec::new();
679 put_varint(&mut b, v);
680 let (got, end) = get_varint(&b, 0).expect("decode");
681 assert_eq!(got, v, "value {v} round-trip");
682 assert_eq!(end, b.len(), "consumed all bytes for {v}");
683 }
684 }
685
686 #[test]
687 fn varint_uses_minimal_encoding() {
688 let mut b = Vec::new();
689 put_varint(&mut b, 63);
690 assert_eq!(b.len(), 1, "6-bit value is one byte");
691 b.clear();
692 put_varint(&mut b, 64);
693 assert_eq!(b.len(), 2, "14-bit value is two bytes");
694 }
695
696 #[test]
697 fn full_packet_round_trips_every_frame() {
698 let p = ControlPacket {
699 ack: Some(AckFrame { ack_through: 70_000 }),
700 nak: Some(NakFrame {
701 block: 12,
702 mask: 0b1011,
703 }),
704 loss: Some(LossFrame {
705 loss_x255: 40,
706 burstiness_x255: 200,
707 owd_trend_class: 2,
708 loss_class: 2,
709 }),
710 timing: Some(TimingFrame {
711 send_ts: 1_234_567,
712 echo_ts: 1_234_000,
713 }),
714 ring: Some(RingFrame {
715 fill_pct: 30,
716 ring_kind: 1,
717 producers: 2,
718 consumers: 3,
719 trend: 1,
720 flags: 1,
721 }),
722 path: Some(PathFrame {
723 ttl: 53,
724 ecn: 0b11,
725 hop_count: 11,
726 ce_count: 4242,
727 ect_count: 99999,
728 }),
729 link: Some(LinkFrame {
730 class: link_class::WIFI,
731 quality: 180,
732 }),
733 loss_acct: Some(LossAcctFrame {
734 seq: 6000,
735 last_recv_seq: 5000,
736 }),
737 pmtu: Some(PmtuFrame { pmtu: 1280 }),
738 bw_probe: vec![
739 BwProbeFrame {
740 probe_id: 7,
741 idx: 0,
742 send_ts: 999,
743 },
744 BwProbeFrame {
745 probe_id: 7,
746 idx: 1,
747 send_ts: 1099,
748 },
749 ],
750 trace: vec![TraceFrame {
751 hop_ttl: 5,
752 probe_id: 7,
753 }],
754 avail_bw: Some(AvailBwFrame {
755 avail_kbps: 45_000,
756 capacity_kbps: 100_000,
757 }),
758 forecast: Some(ForecastFrame {
759 forecast_kbps: 38_500,
760 }),
761 periodicity: Some(PeriodicityFrame {
762 period_ds: 150,
763 secs_to_spike_ds: 42,
764 confidence_x255: 200,
765 }),
766 session_challenge: Some(SessionFrame {
767 epoch: 0xDEAD_BEEF,
768 nonce: 0x0123_4567_89AB_CDEF,
769 }),
770 session_response: Some(SessionFrame {
771 epoch: 0xFEED_FACE,
772 nonce: 0xFEDC_BA98_7654_3210 & NONCE_MASK,
775 }),
776 session_announce: Some(0x1234_5678),
777 };
778 let wire = encode_control(&p);
779 assert_eq!(wire[0], PKT_CONTROL);
780 let got = decode_control(&wire).expect("decode");
781 assert_eq!(got, p, "full packet round-trips");
782 }
783
784 #[test]
785 fn padding_reaches_exact_size_and_still_decodes() {
786 let mut p = ControlPacket::new();
787 p.bw_probe.push(BwProbeFrame {
788 probe_id: 3,
789 idx: 1,
790 send_ts: 42,
791 });
792 let mut wire = encode_control(&p);
793 pad_control_to(&mut wire, 1400);
794 assert_eq!(wire.len(), 1400, "padded to the exact target size");
795 let got = decode_control(&wire).expect("decode");
796 assert_eq!(got.bw_probe, p.bw_probe, "the probe survives the padding");
797 assert!(got.avail_bw.is_none(), "the pad frame is skipped, not misread");
798 }
799
800 #[test]
801 fn empty_packet_is_just_the_tag() {
802 let p = ControlPacket::new();
803 assert!(p.is_empty());
804 let wire = encode_control(&p);
805 assert_eq!(wire, vec![PKT_CONTROL]);
806 assert_eq!(decode_control(&wire).unwrap(), p);
807 }
808
809 #[test]
810 fn unknown_frame_is_skipped_not_fatal() {
811 let mut wire = vec![PKT_CONTROL];
815 wire.push(FrameType::Ack as u8);
816 put_varint(&mut wire, 1);
817 wire.push(9); wire.push(0x7F); put_varint(&mut wire, 4);
820 wire.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
821 wire.push(FrameType::Link as u8);
822 put_varint(&mut wire, 2);
823 wire.extend_from_slice(&[link_class::CELLULAR, 99]);
824
825 let p = decode_control(&wire).expect("decode");
826 assert_eq!(p.ack, Some(AckFrame { ack_through: 9 }));
827 assert_eq!(
828 p.link,
829 Some(LinkFrame {
830 class: link_class::CELLULAR,
831 quality: 99
832 })
833 );
834 }
835
836 #[test]
837 fn truncated_frame_length_aborts_cleanly() {
838 let mut wire = vec![PKT_CONTROL];
841 wire.push(FrameType::Ack as u8);
842 put_varint(&mut wire, 1);
843 wire.push(5);
844 wire.push(FrameType::Pmtu as u8);
845 put_varint(&mut wire, 10); wire.extend_from_slice(&[0x01, 0x02]);
847 let p = decode_control(&wire).expect("decode");
848 assert_eq!(p.ack, Some(AckFrame { ack_through: 5 }));
849 assert_eq!(p.pmtu, None, "truncated frame dropped");
850 }
851
852 #[test]
853 fn non_control_datagram_returns_none() {
854 assert!(decode_control(&[1, 2, 3]).is_none());
855 assert!(decode_control(&[]).is_none());
856 assert!(!is_control(&[2]));
857 assert!(is_control(&[PKT_CONTROL]));
858 }
859}