ndn_packet/lp/
encode.rs

1use bytes::Bytes;
2use ndn_tlv::TlvWriter;
3
4use super::{CachePolicyType, LpHeaders, is_lp_packet, nni};
5use crate::tlv_type;
6
7/// Encode a Nack as an NDNLPv2 LpPacket.
8///
9/// The resulting packet is:
10/// ```text
11/// LpPacket (0x64)
12///   Nack (0x0320)
13///     NackReason (0x0321) = reason code
14///   Fragment (0x50)
15///     <original Interest wire bytes>
16/// ```
17pub fn encode_lp_nack(reason: crate::nack::NackReason, interest_wire: &[u8]) -> Bytes {
18    let mut w = TlvWriter::new();
19    w.write_nested(tlv_type::LP_PACKET, |w| {
20        // Nack header field.
21        w.write_nested(tlv_type::NACK, |w| {
22            let (buf, len) = nni(reason.code());
23            w.write_tlv(tlv_type::NACK_REASON, &buf[..len]);
24        });
25        // Fragment: the original Interest.
26        w.write_tlv(tlv_type::LP_FRAGMENT, interest_wire);
27    });
28    w.finish()
29}
30
31/// Wrap a bare Interest or Data in a minimal NDNLPv2 LpPacket.
32///
33/// If the packet is already an LpPacket (starts with 0x64), returns it unchanged.
34///
35/// ```text
36/// LpPacket (0x64)
37///   Fragment (0x50)
38///     <original packet wire bytes>
39/// ```
40pub fn encode_lp_packet(packet: &[u8]) -> Bytes {
41    if is_lp_packet(packet) {
42        return Bytes::copy_from_slice(packet);
43    }
44    let mut w = TlvWriter::new();
45    w.write_nested(tlv_type::LP_PACKET, |w| {
46        w.write_tlv(tlv_type::LP_FRAGMENT, packet);
47    });
48    w.finish()
49}
50
51/// Encode a reliability-enabled LpPacket with TxSequence, optional fragmentation,
52/// and piggybacked Acks.
53///
54/// `frag_info` is `Some((frag_index, frag_count))` for fragmented packets.
55/// `acks` contains TxSequences to acknowledge.
56pub fn encode_lp_reliable(
57    fragment: &[u8],
58    sequence: u64,
59    frag_info: Option<(u64, u64)>,
60    acks: &[u64],
61) -> Bytes {
62    let mut w = TlvWriter::new();
63    w.write_nested(tlv_type::LP_PACKET, |w| {
64        let (buf, len) = nni(sequence);
65        w.write_tlv(tlv_type::LP_SEQUENCE, &buf[..len]);
66        if let Some((idx, count)) = frag_info {
67            let (buf, len) = nni(idx);
68            w.write_tlv(tlv_type::LP_FRAG_INDEX, &buf[..len]);
69            let (buf, len) = nni(count);
70            w.write_tlv(tlv_type::LP_FRAG_COUNT, &buf[..len]);
71        }
72        for &ack in acks {
73            let (buf, len) = nni(ack);
74            w.write_tlv(tlv_type::LP_ACK, &buf[..len]);
75        }
76        w.write_tlv(tlv_type::LP_FRAGMENT, fragment);
77    });
78    w.finish()
79}
80
81/// Encode a bare Ack-only LpPacket (no fragment payload).
82pub fn encode_lp_acks(acks: &[u64]) -> Bytes {
83    let mut w = TlvWriter::new();
84    w.write_nested(tlv_type::LP_PACKET, |w| {
85        for &ack in acks {
86            let (buf, len) = nni(ack);
87            w.write_tlv(tlv_type::LP_ACK, &buf[..len]);
88        }
89    });
90    w.finish()
91}
92
93/// Encode an LpPacket with optional header fields.
94///
95/// LP header fields are written in increasing TLV-TYPE order as required by the spec.
96pub fn encode_lp_with_headers(fragment: &[u8], headers: &LpHeaders) -> Bytes {
97    let mut w = TlvWriter::new();
98    w.write_nested(tlv_type::LP_PACKET, |w| {
99        // Fields must appear in increasing TLV-TYPE order.
100        // 0x62 PitToken
101        if let Some(ref token) = headers.pit_token {
102            w.write_tlv(tlv_type::LP_PIT_TOKEN, token);
103        }
104        // 0x032C IncomingFaceId
105        if let Some(id) = headers.incoming_face_id {
106            let (buf, len) = nni(id);
107            w.write_tlv(tlv_type::LP_INCOMING_FACE_ID, &buf[..len]);
108        }
109        // 0x0334 CachePolicy
110        if let Some(ref cp) = headers.cache_policy {
111            w.write_nested(tlv_type::LP_CACHE_POLICY, |w| {
112                let code = match cp {
113                    CachePolicyType::NoCache => 1u64,
114                    CachePolicyType::Other(c) => *c,
115                };
116                let (buf, len) = nni(code);
117                w.write_tlv(tlv_type::LP_CACHE_POLICY_TYPE, &buf[..len]);
118            });
119        }
120        // 0x0340 CongestionMark
121        if let Some(mark) = headers.congestion_mark {
122            let (buf, len) = nni(mark);
123            w.write_tlv(tlv_type::LP_CONGESTION_MARK, &buf[..len]);
124        }
125        // 0x50 Fragment (last)
126        w.write_tlv(tlv_type::LP_FRAGMENT, fragment);
127    });
128    w.finish()
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::encode::encode_interest;
135    use crate::lp::{LpPacket, is_lp_packet};
136    use crate::nack::NackReason;
137    use crate::{Interest, Name, NameComponent};
138    use bytes::Bytes;
139
140    fn name(comps: &[&[u8]]) -> Name {
141        Name::from_components(
142            comps
143                .iter()
144                .map(|c| NameComponent::generic(Bytes::copy_from_slice(c))),
145        )
146    }
147
148    #[test]
149    fn is_lp_packet_checks_first_byte() {
150        assert!(is_lp_packet(&[0x64, 0x00]));
151        assert!(!is_lp_packet(&[0x05, 0x00]));
152        assert!(!is_lp_packet(&[]));
153    }
154
155    #[test]
156    fn encode_lp_packet_wraps_bare_interest() {
157        let n = name(&[b"test"]);
158        let interest_wire = encode_interest(&n, None);
159
160        let lp_wire = encode_lp_packet(&interest_wire);
161        assert!(is_lp_packet(&lp_wire));
162
163        let lp = LpPacket::decode(lp_wire).unwrap();
164        assert!(lp.nack.is_none());
165        let interest = Interest::decode(lp.fragment.unwrap()).unwrap();
166        assert_eq!(*interest.name, n);
167    }
168
169    #[test]
170    fn encode_lp_packet_passthrough_existing_lp() {
171        let n = name(&[b"test"]);
172        let interest_wire = encode_interest(&n, None);
173        let lp_wire = encode_lp_nack(NackReason::NoRoute, &interest_wire);
174
175        // Wrapping an existing LpPacket should return it unchanged.
176        let rewrapped = encode_lp_packet(&lp_wire);
177        assert_eq!(rewrapped, lp_wire);
178    }
179}