ndn_packet/lp/
fragment.rs

1use super::decode_be_u64;
2
3/// Result of lightweight fragment extraction.
4///
5/// Returned by [`extract_fragment`] for packets that carry fragmentation fields
6/// (`FragCount > 1`).  Holds the minimum information needed for reassembly
7/// without parsing Nack, CongestionMark, or other LpPacket headers.
8pub struct FragmentHeader {
9    pub sequence: u64,
10    pub frag_index: u64,
11    pub frag_count: u64,
12    /// Byte range of the Fragment TLV value within the original raw buffer.
13    pub frag_start: usize,
14    pub frag_end: usize,
15}
16
17/// Lightweight fragment extraction from an LpPacket.
18///
19/// Scans the TLV fields for Sequence, FragIndex, FragCount, and Fragment
20/// **without** creating `Bytes` sub-slices, parsing Nack headers, or allocating.
21/// Returns `Some` only if the packet is a multi-fragment LpPacket (`frag_count > 1`).
22///
23/// This is the hot-path parser for the fragment sieve — unfragmented LpPackets,
24/// Nacks, and bare Interest/Data fall through to the full `LpPacket::decode`.
25pub fn extract_fragment(raw: &[u8]) -> Option<FragmentHeader> {
26    if raw.first() != Some(&0x64) {
27        return None;
28    }
29    // Read outer TLV: type (0x64) + length.
30    let (_, type_len) = ndn_tlv::read_varu64(raw).ok()?;
31    let (outer_len, len_len) = ndn_tlv::read_varu64(&raw[type_len..]).ok()?;
32    let header_len = type_len + len_len;
33    let inner = raw.get(header_len..header_len + outer_len as usize)?;
34
35    let mut pos = 0;
36    let mut sequence = None;
37    let mut frag_index = None;
38    let mut frag_count = None;
39    let mut frag_start = 0;
40    let mut frag_end = 0;
41
42    while pos < inner.len() {
43        let (t, tn) = ndn_tlv::read_varu64(&inner[pos..]).ok()?;
44        pos += tn;
45        let (l, ln) = ndn_tlv::read_varu64(&inner[pos..]).ok()?;
46        pos += ln;
47        let l = l as usize;
48        if pos + l > inner.len() {
49            return None;
50        }
51        match t {
52            0x51 => sequence = Some(decode_be_u64(&inner[pos..pos + l])),
53            0x52 => frag_index = Some(decode_be_u64(&inner[pos..pos + l])),
54            0x53 => {
55                let c = decode_be_u64(&inner[pos..pos + l]);
56                if c <= 1 {
57                    return None;
58                } // Not fragmented — let full decode handle it.
59                frag_count = Some(c);
60            }
61            0x50 => {
62                // frag_start relative to raw, not inner.
63                frag_start = header_len + pos;
64                frag_end = frag_start + l;
65            }
66            _ => {}
67        }
68        pos += l;
69    }
70
71    Some(FragmentHeader {
72        sequence: sequence?,
73        frag_index: frag_index?,
74        frag_count: frag_count?,
75        frag_start,
76        frag_end,
77    })
78}
79
80/// Fast-path extraction of Sequence and Ack fields from a raw LpPacket.
81///
82/// Scans for Sequence (0x51) and Ack (0x0344) TLVs without allocating `Bytes`.
83/// Returns `(tx_sequence, acks)`. Used only for reliability-enabled faces.
84pub fn extract_acks(raw: &[u8]) -> (Option<u64>, smallvec::SmallVec<[u64; 8]>) {
85    let mut tx_seq = None;
86    let mut acks = smallvec::SmallVec::new();
87
88    if raw.first() != Some(&0x64) {
89        return (tx_seq, acks);
90    }
91    let Some((_, type_len)) = ndn_tlv::read_varu64(raw).ok() else {
92        return (tx_seq, acks);
93    };
94    let Some((outer_len, len_len)) = ndn_tlv::read_varu64(&raw[type_len..]).ok() else {
95        return (tx_seq, acks);
96    };
97    let header_len = type_len + len_len;
98    let Some(inner) = raw.get(header_len..header_len + outer_len as usize) else {
99        return (tx_seq, acks);
100    };
101
102    let mut pos = 0;
103    while pos < inner.len() {
104        let Some((t, tn)) = ndn_tlv::read_varu64(&inner[pos..]).ok() else {
105            break;
106        };
107        pos += tn;
108        let Some((l, ln)) = ndn_tlv::read_varu64(&inner[pos..]).ok() else {
109            break;
110        };
111        pos += ln;
112        let l = l as usize;
113        if pos + l > inner.len() {
114            break;
115        }
116        match t {
117            0x51 => tx_seq = Some(decode_be_u64(&inner[pos..pos + l])),
118            0x0344 => acks.push(decode_be_u64(&inner[pos..pos + l])),
119            _ => {}
120        }
121        pos += l;
122    }
123    (tx_seq, acks)
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::encode::encode_interest;
130    use crate::lp::{LpPacket, encode_lp_acks, encode_lp_packet, encode_lp_reliable};
131    use crate::{Name, NameComponent};
132    use bytes::Bytes;
133    use ndn_tlv::TlvWriter;
134
135    fn name(comps: &[&[u8]]) -> Name {
136        Name::from_components(
137            comps
138                .iter()
139                .map(|c| NameComponent::generic(Bytes::copy_from_slice(c))),
140        )
141    }
142
143    #[test]
144    fn extract_fragment_returns_correct_fields() {
145        let n = name(&[b"test"]);
146        let interest_wire = encode_interest(&n, None);
147
148        let mut w = TlvWriter::new();
149        w.write_nested(crate::tlv_type::LP_PACKET, |w| {
150            w.write_tlv(crate::tlv_type::LP_SEQUENCE, &42u64.to_be_bytes());
151            w.write_tlv(crate::tlv_type::LP_FRAG_INDEX, &[1]);
152            w.write_tlv(crate::tlv_type::LP_FRAG_COUNT, &[3]);
153            w.write_tlv(crate::tlv_type::LP_FRAGMENT, &interest_wire);
154        });
155        let raw = w.finish();
156
157        let hdr = extract_fragment(&raw).unwrap();
158        assert_eq!(hdr.sequence, 42);
159        assert_eq!(hdr.frag_index, 1);
160        assert_eq!(hdr.frag_count, 3);
161        assert_eq!(&raw[hdr.frag_start..hdr.frag_end], &interest_wire[..]);
162    }
163
164    #[test]
165    fn extract_fragment_returns_none_for_unfragmented() {
166        let n = name(&[b"test"]);
167        let interest_wire = encode_interest(&n, None);
168        let lp_wire = encode_lp_packet(&interest_wire);
169        assert!(extract_fragment(&lp_wire).is_none());
170    }
171
172    #[test]
173    fn extract_fragment_returns_none_for_single_fragment() {
174        let mut w = TlvWriter::new();
175        w.write_nested(crate::tlv_type::LP_PACKET, |w| {
176            w.write_tlv(crate::tlv_type::LP_SEQUENCE, &[0]);
177            w.write_tlv(crate::tlv_type::LP_FRAG_INDEX, &[0]);
178            w.write_tlv(crate::tlv_type::LP_FRAG_COUNT, &[1]); // count=1, not fragmented
179            w.write_tlv(crate::tlv_type::LP_FRAGMENT, &[0x05, 0x00]);
180        });
181        assert!(extract_fragment(&w.finish()).is_none());
182    }
183
184    #[test]
185    fn extract_fragment_matches_full_decode() {
186        // Ensure extract_fragment and LpPacket::decode agree on fragment content.
187        use crate::fragment::fragment_packet;
188        let data: Vec<u8> = (0..3000).map(|i| (i % 256) as u8).collect();
189        let frags = fragment_packet(&data, 500, 99);
190        for frag_bytes in &frags {
191            let hdr = extract_fragment(frag_bytes).unwrap();
192            let lp = LpPacket::decode(Bytes::copy_from_slice(frag_bytes)).unwrap();
193            assert_eq!(hdr.sequence, lp.sequence.unwrap());
194            assert_eq!(hdr.frag_index, lp.frag_index.unwrap());
195            assert_eq!(hdr.frag_count, lp.frag_count.unwrap());
196            assert_eq!(
197                &frag_bytes[hdr.frag_start..hdr.frag_end],
198                &lp.fragment.unwrap()[..]
199            );
200        }
201    }
202
203    #[test]
204    fn extract_acks_from_reliable_packet() {
205        let wire = encode_lp_reliable(&[0x05, 0x00], 42, None, &[10, 20, 30]);
206        let (seq, acks) = extract_acks(&wire);
207        assert_eq!(seq, Some(42));
208        assert_eq!(&acks[..], &[10, 20, 30]);
209    }
210
211    #[test]
212    fn extract_acks_from_ack_only() {
213        let wire = encode_lp_acks(&[7, 8]);
214        let (seq, acks) = extract_acks(&wire);
215        assert_eq!(seq, None);
216        assert_eq!(&acks[..], &[7, 8]);
217    }
218
219    #[test]
220    fn extract_acks_from_plain_lp() {
221        let n = name(&[b"test"]);
222        let interest_wire = encode_interest(&n, None);
223        let wire = encode_lp_packet(&interest_wire);
224        let (seq, acks) = extract_acks(&wire);
225        assert_eq!(seq, None);
226        assert!(acks.is_empty());
227    }
228}