Skip to main content

pim_protocol/
fragment_frame.rs

1//! Fragmentation of large mesh data payloads.
2//!
3//! Large IP packets are split into `FragmentFrame`s before being encapsulated
4//! in a `MeshDataFrame` so that each hop stays within the mesh MTU.
5//!
6//! # Wire format
7//!
8//! ```text
9//! fragment_id   (4 bytes, big-endian u32)
10//! fragment_offset (2 bytes, big-endian u16)  — byte offset in original packet
11//! total_length  (2 bytes, big-endian u16)   — total original packet length
12//! payload       (variable)
13//! ```
14
15/// Maximum payload bytes per fragment (leaves room for outer headers).
16pub const MAX_FRAGMENT_PAYLOAD: usize = 1200;
17
18/// Size of the fixed fragment header in bytes.
19pub const FRAGMENT_HEADER_SIZE: usize = 8;
20
21/// A single fragment of a larger IP packet.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct FragmentFrame {
24    /// Identifies the original packet being fragmented (monotonically increasing
25    /// per sender; unique enough within the reassembly timeout window).
26    pub fragment_id: u32,
27    /// Byte offset of `payload` within the original packet.
28    pub fragment_offset: u16,
29    /// Total byte length of the original (unfragmented) packet.
30    pub total_length: u16,
31    /// Fragment payload.
32    pub payload: bytes::Bytes,
33}
34
35impl FragmentFrame {
36    /// Serialize to wire bytes.
37    pub fn serialize(&self) -> Vec<u8> {
38        let mut buf = Vec::with_capacity(FRAGMENT_HEADER_SIZE + self.payload.len());
39        buf.extend_from_slice(&self.fragment_id.to_be_bytes());
40        buf.extend_from_slice(&self.fragment_offset.to_be_bytes());
41        buf.extend_from_slice(&self.total_length.to_be_bytes());
42        buf.extend_from_slice(&self.payload);
43        buf
44    }
45
46    /// Deserialize from wire bytes.  Returns `None` if the buffer is too short
47    /// or the contained offsets are nonsensical.
48    pub fn deserialize(buf: &[u8]) -> Option<Self> {
49        if buf.len() < FRAGMENT_HEADER_SIZE {
50            return None;
51        }
52        let fragment_id = u32::from_be_bytes(buf[0..4].try_into().ok()?);
53        let fragment_offset = u16::from_be_bytes(buf[4..6].try_into().ok()?);
54        let total_length = u16::from_be_bytes(buf[6..8].try_into().ok()?);
55        let payload = bytes::Bytes::copy_from_slice(&buf[8..]);
56
57        // Basic sanity checks.
58        let end = (fragment_offset as usize).checked_add(payload.len())?;
59        if end > total_length as usize {
60            return None;
61        }
62
63        Some(Self {
64            fragment_id,
65            fragment_offset,
66            total_length,
67            payload,
68        })
69    }
70
71    /// `true` if this is the last fragment (its data reaches the end of the
72    /// original packet).
73    pub fn is_last(&self) -> bool {
74        self.fragment_offset as usize + self.payload.len() == self.total_length as usize
75    }
76}
77
78/// Split `data` into [`FragmentFrame`]s using `fragment_id` as the shared
79/// identifier.  Each fragment carries at most [`MAX_FRAGMENT_PAYLOAD`] bytes.
80///
81/// Returns an empty `Vec` only when `data` is empty.
82pub fn fragment_packet(data: bytes::Bytes, fragment_id: u32) -> Vec<FragmentFrame> {
83    if data.is_empty() {
84        return Vec::new();
85    }
86
87    let total_length = data.len() as u16;
88    let mut frames = Vec::with_capacity(data.len().div_ceil(MAX_FRAGMENT_PAYLOAD));
89    let mut offset = 0usize;
90
91    while offset < data.len() {
92        let end = (offset + MAX_FRAGMENT_PAYLOAD).min(data.len());
93        frames.push(FragmentFrame {
94            fragment_id,
95            fragment_offset: offset as u16,
96            total_length,
97            payload: data.slice(offset..end),
98        });
99        offset = end;
100    }
101
102    frames
103}
104
105// ── Tests ─────────────────────────────────────────────────────────────────────
106
107#[cfg(test)]
108mod tests;