Skip to main content

fips_core/mmp/
report.rs

1//! MMP report wire format: SenderReport and ReceiverReport.
2//!
3//! Serialization and deserialization for the two report types exchanged
4//! between link-layer peers. Wire format follows the MMP design doc.
5
6use crate::protocol::ProtocolError;
7
8// ============================================================================
9// SenderReport (msg_type 0x01, 48-byte body including type byte)
10// ============================================================================
11
12/// Link-layer sender report.
13///
14/// Wire layout (48 bytes total, sent as link message):
15/// ```text
16/// [0]    msg_type = 0x01
17/// [1-3]  reserved (zero)
18/// [4-11] interval_start_counter: u64 LE
19/// [12-19] interval_end_counter: u64 LE
20/// [20-23] interval_start_timestamp: u32 LE
21/// [24-27] interval_end_timestamp: u32 LE
22/// [28-31] interval_bytes_sent: u32 LE
23/// [32-39] cumulative_packets_sent: u64 LE
24/// [40-47] cumulative_bytes_sent: u64 LE
25/// ```
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct SenderReport {
28    pub interval_start_counter: u64,
29    pub interval_end_counter: u64,
30    pub interval_start_timestamp: u32,
31    pub interval_end_timestamp: u32,
32    pub interval_bytes_sent: u32,
33    pub cumulative_packets_sent: u64,
34    pub cumulative_bytes_sent: u64,
35}
36
37/// ReceiverReport (msg_type 0x02, 68-byte body including type byte)
38///
39/// Wire layout (68 bytes total, sent as link message):
40/// ```text
41/// [0]    msg_type = 0x02
42/// [1-3]  reserved (zero)
43/// [4-11] highest_counter: u64 LE
44/// [12-19] cumulative_packets_recv: u64 LE
45/// [20-27] cumulative_bytes_recv: u64 LE
46/// [28-31] timestamp_echo: u32 LE
47/// [32-33] dwell_time: u16 LE
48/// [34-35] max_burst_loss: u16 LE
49/// [36-37] mean_burst_loss: u16 LE (u8.8 fixed-point)
50/// [38-39] reserved: u16 LE
51/// [40-43] jitter: u32 LE (microseconds)
52/// [44-47] ecn_ce_count: u32 LE
53/// [48-51] owd_trend: i32 LE (µs/s)
54/// [52-55] burst_loss_count: u32 LE
55/// [56-59] cumulative_reorder_count: u32 LE
56/// [60-63] interval_packets_recv: u32 LE
57/// [64-67] interval_bytes_recv: u32 LE
58/// ```
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct ReceiverReport {
61    pub highest_counter: u64,
62    pub cumulative_packets_recv: u64,
63    pub cumulative_bytes_recv: u64,
64    pub timestamp_echo: u32,
65    pub dwell_time: u16,
66    pub max_burst_loss: u16,
67    pub mean_burst_loss: u16,
68    pub jitter: u32,
69    pub ecn_ce_count: u32,
70    pub owd_trend: i32,
71    pub burst_loss_count: u32,
72    pub cumulative_reorder_count: u32,
73    pub interval_packets_recv: u32,
74    pub interval_bytes_recv: u32,
75}
76
77// Encode/decode will be implemented in Step 2.
78
79impl SenderReport {
80    /// Encode to wire format (48 bytes: msg_type + 3 reserved + 44 payload).
81    pub fn encode(&self) -> Vec<u8> {
82        let mut buf = Vec::with_capacity(48);
83        buf.push(0x01); // msg_type
84        buf.extend_from_slice(&[0u8; 3]); // reserved
85        buf.extend_from_slice(&self.interval_start_counter.to_le_bytes());
86        buf.extend_from_slice(&self.interval_end_counter.to_le_bytes());
87        buf.extend_from_slice(&self.interval_start_timestamp.to_le_bytes());
88        buf.extend_from_slice(&self.interval_end_timestamp.to_le_bytes());
89        buf.extend_from_slice(&self.interval_bytes_sent.to_le_bytes());
90        buf.extend_from_slice(&self.cumulative_packets_sent.to_le_bytes());
91        buf.extend_from_slice(&self.cumulative_bytes_sent.to_le_bytes());
92        buf
93    }
94
95    /// Decode from payload after msg_type byte has been consumed.
96    ///
97    /// `payload` starts at the reserved bytes (offset 1 in the wire format).
98    pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
99        if payload.len() < 47 {
100            return Err(ProtocolError::MessageTooShort {
101                expected: 47,
102                got: payload.len(),
103            });
104        }
105        // Skip 3 reserved bytes
106        let p = &payload[3..];
107        Ok(Self {
108            interval_start_counter: u64::from_le_bytes(p[0..8].try_into().unwrap()),
109            interval_end_counter: u64::from_le_bytes(p[8..16].try_into().unwrap()),
110            interval_start_timestamp: u32::from_le_bytes(p[16..20].try_into().unwrap()),
111            interval_end_timestamp: u32::from_le_bytes(p[20..24].try_into().unwrap()),
112            interval_bytes_sent: u32::from_le_bytes(p[24..28].try_into().unwrap()),
113            cumulative_packets_sent: u64::from_le_bytes(p[28..36].try_into().unwrap()),
114            cumulative_bytes_sent: u64::from_le_bytes(p[36..44].try_into().unwrap()),
115        })
116    }
117}
118
119impl ReceiverReport {
120    /// Encode to wire format (68 bytes: msg_type + 3 reserved + 64 payload).
121    pub fn encode(&self) -> Vec<u8> {
122        let mut buf = Vec::with_capacity(68);
123        buf.push(0x02); // msg_type
124        buf.extend_from_slice(&[0u8; 3]); // reserved
125        buf.extend_from_slice(&self.highest_counter.to_le_bytes());
126        buf.extend_from_slice(&self.cumulative_packets_recv.to_le_bytes());
127        buf.extend_from_slice(&self.cumulative_bytes_recv.to_le_bytes());
128        buf.extend_from_slice(&self.timestamp_echo.to_le_bytes());
129        buf.extend_from_slice(&self.dwell_time.to_le_bytes());
130        buf.extend_from_slice(&self.max_burst_loss.to_le_bytes());
131        buf.extend_from_slice(&self.mean_burst_loss.to_le_bytes());
132        buf.extend_from_slice(&[0u8; 2]); // reserved
133        buf.extend_from_slice(&self.jitter.to_le_bytes());
134        buf.extend_from_slice(&self.ecn_ce_count.to_le_bytes());
135        buf.extend_from_slice(&self.owd_trend.to_le_bytes());
136        buf.extend_from_slice(&self.burst_loss_count.to_le_bytes());
137        buf.extend_from_slice(&self.cumulative_reorder_count.to_le_bytes());
138        buf.extend_from_slice(&self.interval_packets_recv.to_le_bytes());
139        buf.extend_from_slice(&self.interval_bytes_recv.to_le_bytes());
140        buf
141    }
142
143    /// Decode from payload after msg_type byte has been consumed.
144    ///
145    /// `payload` starts at the reserved bytes (offset 1 in the wire format).
146    pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
147        if payload.len() < 67 {
148            return Err(ProtocolError::MessageTooShort {
149                expected: 67,
150                got: payload.len(),
151            });
152        }
153        // Skip 3 reserved bytes
154        let p = &payload[3..];
155        Ok(Self {
156            highest_counter: u64::from_le_bytes(p[0..8].try_into().unwrap()),
157            cumulative_packets_recv: u64::from_le_bytes(p[8..16].try_into().unwrap()),
158            cumulative_bytes_recv: u64::from_le_bytes(p[16..24].try_into().unwrap()),
159            timestamp_echo: u32::from_le_bytes(p[24..28].try_into().unwrap()),
160            dwell_time: u16::from_le_bytes(p[28..30].try_into().unwrap()),
161            max_burst_loss: u16::from_le_bytes(p[30..32].try_into().unwrap()),
162            mean_burst_loss: u16::from_le_bytes(p[32..34].try_into().unwrap()),
163            // skip 2 reserved bytes at p[34..36]
164            jitter: u32::from_le_bytes(p[36..40].try_into().unwrap()),
165            ecn_ce_count: u32::from_le_bytes(p[40..44].try_into().unwrap()),
166            owd_trend: i32::from_le_bytes(p[44..48].try_into().unwrap()),
167            burst_loss_count: u32::from_le_bytes(p[48..52].try_into().unwrap()),
168            cumulative_reorder_count: u32::from_le_bytes(p[52..56].try_into().unwrap()),
169            interval_packets_recv: u32::from_le_bytes(p[56..60].try_into().unwrap()),
170            interval_bytes_recv: u32::from_le_bytes(p[60..64].try_into().unwrap()),
171        })
172    }
173}
174
175// ============================================================================
176// Conversions between link-layer and session-layer report types
177// ============================================================================
178
179use crate::protocol::{SessionReceiverReport, SessionSenderReport};
180
181impl From<&SenderReport> for SessionSenderReport {
182    fn from(r: &SenderReport) -> Self {
183        Self {
184            interval_start_counter: r.interval_start_counter,
185            interval_end_counter: r.interval_end_counter,
186            interval_start_timestamp: r.interval_start_timestamp,
187            interval_end_timestamp: r.interval_end_timestamp,
188            interval_bytes_sent: r.interval_bytes_sent,
189            cumulative_packets_sent: r.cumulative_packets_sent,
190            cumulative_bytes_sent: r.cumulative_bytes_sent,
191        }
192    }
193}
194
195impl From<&SessionSenderReport> for SenderReport {
196    fn from(r: &SessionSenderReport) -> Self {
197        Self {
198            interval_start_counter: r.interval_start_counter,
199            interval_end_counter: r.interval_end_counter,
200            interval_start_timestamp: r.interval_start_timestamp,
201            interval_end_timestamp: r.interval_end_timestamp,
202            interval_bytes_sent: r.interval_bytes_sent,
203            cumulative_packets_sent: r.cumulative_packets_sent,
204            cumulative_bytes_sent: r.cumulative_bytes_sent,
205        }
206    }
207}
208
209impl From<&ReceiverReport> for SessionReceiverReport {
210    fn from(r: &ReceiverReport) -> Self {
211        Self {
212            highest_counter: r.highest_counter,
213            cumulative_packets_recv: r.cumulative_packets_recv,
214            cumulative_bytes_recv: r.cumulative_bytes_recv,
215            timestamp_echo: r.timestamp_echo,
216            dwell_time: r.dwell_time,
217            max_burst_loss: r.max_burst_loss,
218            mean_burst_loss: r.mean_burst_loss,
219            jitter: r.jitter,
220            ecn_ce_count: r.ecn_ce_count,
221            owd_trend: r.owd_trend,
222            burst_loss_count: r.burst_loss_count,
223            cumulative_reorder_count: r.cumulative_reorder_count,
224            interval_packets_recv: r.interval_packets_recv,
225            interval_bytes_recv: r.interval_bytes_recv,
226        }
227    }
228}
229
230impl From<&SessionReceiverReport> for ReceiverReport {
231    fn from(r: &SessionReceiverReport) -> Self {
232        Self {
233            highest_counter: r.highest_counter,
234            cumulative_packets_recv: r.cumulative_packets_recv,
235            cumulative_bytes_recv: r.cumulative_bytes_recv,
236            timestamp_echo: r.timestamp_echo,
237            dwell_time: r.dwell_time,
238            max_burst_loss: r.max_burst_loss,
239            mean_burst_loss: r.mean_burst_loss,
240            jitter: r.jitter,
241            ecn_ce_count: r.ecn_ce_count,
242            owd_trend: r.owd_trend,
243            burst_loss_count: r.burst_loss_count,
244            cumulative_reorder_count: r.cumulative_reorder_count,
245            interval_packets_recv: r.interval_packets_recv,
246            interval_bytes_recv: r.interval_bytes_recv,
247        }
248    }
249}
250
251// ============================================================================
252// Tests
253// ============================================================================
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    fn sample_sender_report() -> SenderReport {
260        SenderReport {
261            interval_start_counter: 100,
262            interval_end_counter: 200,
263            interval_start_timestamp: 5000,
264            interval_end_timestamp: 6000,
265            interval_bytes_sent: 50_000,
266            cumulative_packets_sent: 10_000,
267            cumulative_bytes_sent: 5_000_000,
268        }
269    }
270
271    fn sample_receiver_report() -> ReceiverReport {
272        ReceiverReport {
273            highest_counter: 195,
274            cumulative_packets_recv: 9_500,
275            cumulative_bytes_recv: 4_750_000,
276            timestamp_echo: 5900,
277            dwell_time: 5,
278            max_burst_loss: 3,
279            mean_burst_loss: 384, // 1.5 in u8.8
280            jitter: 1200,
281            ecn_ce_count: 0,
282            owd_trend: -50,
283            burst_loss_count: 2,
284            cumulative_reorder_count: 10,
285            interval_packets_recv: 95,
286            interval_bytes_recv: 47_500,
287        }
288    }
289
290    #[test]
291    fn test_sender_report_encode_size() {
292        let sr = sample_sender_report();
293        let encoded = sr.encode();
294        assert_eq!(encoded.len(), 48);
295        assert_eq!(encoded[0], 0x01); // msg_type
296    }
297
298    #[test]
299    fn test_sender_report_roundtrip() {
300        let sr = sample_sender_report();
301        let encoded = sr.encode();
302        // decode expects payload after msg_type
303        let decoded = SenderReport::decode(&encoded[1..]).unwrap();
304        assert_eq!(sr, decoded);
305    }
306
307    #[test]
308    fn test_sender_report_too_short() {
309        let result = SenderReport::decode(&[0u8; 10]);
310        assert!(result.is_err());
311    }
312
313    #[test]
314    fn test_receiver_report_encode_size() {
315        let rr = sample_receiver_report();
316        let encoded = rr.encode();
317        assert_eq!(encoded.len(), 68);
318        assert_eq!(encoded[0], 0x02); // msg_type
319    }
320
321    #[test]
322    fn test_receiver_report_roundtrip() {
323        let rr = sample_receiver_report();
324        let encoded = rr.encode();
325        // decode expects payload after msg_type
326        let decoded = ReceiverReport::decode(&encoded[1..]).unwrap();
327        assert_eq!(rr, decoded);
328    }
329
330    #[test]
331    fn test_receiver_report_too_short() {
332        let result = ReceiverReport::decode(&[0u8; 10]);
333        assert!(result.is_err());
334    }
335
336    #[test]
337    fn test_sender_report_zero_values() {
338        let sr = SenderReport {
339            interval_start_counter: 0,
340            interval_end_counter: 0,
341            interval_start_timestamp: 0,
342            interval_end_timestamp: 0,
343            interval_bytes_sent: 0,
344            cumulative_packets_sent: 0,
345            cumulative_bytes_sent: 0,
346        };
347        let encoded = sr.encode();
348        let decoded = SenderReport::decode(&encoded[1..]).unwrap();
349        assert_eq!(sr, decoded);
350    }
351
352    #[test]
353    fn test_receiver_report_max_values() {
354        let rr = ReceiverReport {
355            highest_counter: u64::MAX,
356            cumulative_packets_recv: u64::MAX,
357            cumulative_bytes_recv: u64::MAX,
358            timestamp_echo: u32::MAX,
359            dwell_time: u16::MAX,
360            max_burst_loss: u16::MAX,
361            mean_burst_loss: u16::MAX,
362            jitter: u32::MAX,
363            ecn_ce_count: u32::MAX,
364            owd_trend: i32::MAX,
365            burst_loss_count: u32::MAX,
366            cumulative_reorder_count: u32::MAX,
367            interval_packets_recv: u32::MAX,
368            interval_bytes_recv: u32::MAX,
369        };
370        let encoded = rr.encode();
371        let decoded = ReceiverReport::decode(&encoded[1..]).unwrap();
372        assert_eq!(rr, decoded);
373    }
374
375    #[test]
376    fn test_receiver_report_negative_owd_trend() {
377        let rr = ReceiverReport {
378            owd_trend: -12345,
379            ..sample_receiver_report()
380        };
381        let encoded = rr.encode();
382        let decoded = ReceiverReport::decode(&encoded[1..]).unwrap();
383        assert_eq!(decoded.owd_trend, -12345);
384    }
385}