ndn_packet/encode/
mod.rs

1/// Packet encoding utilities.
2///
3/// Produces minimal wire-format Interest and Data TLVs using `TlvWriter`.
4/// Intended for applications and the management plane, not the forwarding
5/// pipeline (which operates on already-encoded `Bytes`).
6mod data;
7mod interest;
8
9pub use data::DataBuilder;
10pub use interest::{InterestBuilder, encode_interest, ensure_nonce};
11
12use std::sync::atomic::{AtomicU32, Ordering};
13
14use bytes::Bytes;
15use ndn_tlv::TlvWriter;
16
17use crate::{Name, tlv_type};
18
19// ─── Public API ───────────────────────────────────────────────────────────────
20
21/// Encode a Data TLV signed with `DigestSha256`.
22///
23/// Computes `SHA-256(signed_region)` and embeds it as the signature value.
24/// No key locator is included — DigestSha256 is self-contained integrity
25/// protection that requires no certificate or trust anchor.
26///
27/// `FreshnessPeriod` is set to 0 so management responses are never served
28/// from cache.
29#[cfg(feature = "std")]
30pub fn encode_data_unsigned(name: &Name, content: &[u8]) -> Bytes {
31    DataBuilder::new(name.clone(), content)
32        .freshness(std::time::Duration::ZERO)
33        .sign_digest_sha256()
34}
35
36/// Encode a Nack as an NDNLPv2 LpPacket wrapping the original Interest.
37///
38/// The resulting packet is an LpPacket (0x64) containing:
39/// - Nack header (0x0320) with NackReason (0x0321)
40/// - Fragment (0x50) containing the original Interest wire bytes
41///
42/// `interest_wire` must be a complete Interest TLV (type + length + value).
43pub fn encode_nack(reason: crate::NackReason, interest_wire: &[u8]) -> Bytes {
44    crate::lp::encode_lp_nack(reason, interest_wire)
45}
46
47// ─── Shared helpers ──────────────────────────────────────────────────────────
48
49/// Encode a non-negative integer (NNI) using minimal NDN TLV encoding.
50///
51/// Per NDN Packet Format v0.3 §1.2, a NonNegativeInteger is 1, 2, 4, or 8
52/// bytes in network byte order. The shortest valid encoding SHOULD be used.
53///
54/// Returns a `(buffer, length)` pair — use `&buf[..len]` as the TLV value.
55pub(crate) fn nni(val: u64) -> ([u8; 8], usize) {
56    let be = val.to_be_bytes();
57    if val <= 0xFF {
58        ([be[7], 0, 0, 0, 0, 0, 0, 0], 1)
59    } else if val <= 0xFFFF {
60        ([be[6], be[7], 0, 0, 0, 0, 0, 0], 2)
61    } else if val <= 0xFFFF_FFFF {
62        ([be[4], be[5], be[6], be[7], 0, 0, 0, 0], 4)
63    } else {
64        (be, 8)
65    }
66}
67
68/// Write a TLV element whose value is a NonNegativeInteger.
69pub(super) fn write_nni(w: &mut TlvWriter, typ: u64, val: u64) {
70    let (buf, len) = nni(val);
71    w.write_tlv(typ, &buf[..len]);
72}
73
74/// Write a `Name` TLV into an in-progress writer, preserving each component's
75/// original type code (e.g. `0x08` generic, `0x01` ImplicitSha256Digest).
76pub(super) fn write_name(w: &mut TlvWriter, name: &Name) {
77    w.write_nested(tlv_type::NAME, |w| {
78        for comp in name.components() {
79            w.write_tlv(comp.typ, &comp.value);
80        }
81    });
82}
83
84/// Produce a per-process-unique 4-byte nonce.
85///
86/// Combines a monotonically-increasing per-process counter with the low 16 bits
87/// of the process ID.  Sufficient for loop detection; not cryptographically
88/// random.
89pub(super) fn next_nonce() -> u32 {
90    static COUNTER: AtomicU32 = AtomicU32::new(0);
91    let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
92    (std::process::id() << 16).wrapping_add(seq)
93}
94
95/// Generate 8 random bytes for SignatureNonce anti-replay.
96///
97/// Uses `ring::rand::SystemRandom` which is cryptographically secure.
98pub(super) fn rand_nonce_bytes() -> [u8; 8] {
99    let mut buf = [0u8; 8];
100    ring::rand::SecureRandom::fill(&ring::rand::SystemRandom::new(), &mut buf)
101        .expect("system RNG failed");
102    buf
103}
104
105// ─── Tests ────────────────────────────────────────────────────────────────────
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::{Data, NameComponent};
111    use bytes::Bytes;
112    use std::time::Duration;
113
114    pub(super) fn name(components: &[&[u8]]) -> Name {
115        Name::from_components(
116            components
117                .iter()
118                .map(|c| NameComponent::generic(Bytes::copy_from_slice(c))),
119        )
120    }
121
122    #[test]
123    fn data_roundtrip_name_and_content() {
124        let n = name(&[b"localhost", b"ndn-ctl", b"get-stats"]);
125        let content = br#"{"status":"ok","pit_size":42}"#;
126        let bytes = encode_data_unsigned(&n, content);
127        let data = Data::decode(bytes).unwrap();
128        assert_eq!(*data.name, n);
129        assert_eq!(data.content().map(|b| b.as_ref()), Some(content.as_ref()));
130    }
131
132    #[test]
133    fn data_freshness_is_zero() {
134        let n = name(&[b"test"]);
135        let bytes = encode_data_unsigned(&n, b"hello");
136        let data = Data::decode(bytes).unwrap();
137        let mi = data.meta_info().expect("meta_info present");
138        assert_eq!(mi.freshness_period, Some(Duration::from_millis(0)));
139    }
140
141    #[test]
142    fn nack_roundtrip() {
143        use crate::{Nack, NackReason};
144        let n = name(&[b"test", b"nack"]);
145        let interest_wire = encode_interest(&n, None);
146        let nack_wire = encode_nack(NackReason::NoRoute, &interest_wire);
147        let nack = Nack::decode(nack_wire).unwrap();
148        assert_eq!(nack.reason, NackReason::NoRoute);
149        assert_eq!(*nack.interest.name, n);
150    }
151
152    #[test]
153    fn nack_congestion_roundtrip() {
154        use crate::{Nack, NackReason};
155        let n = name(&[b"hello"]);
156        let interest_wire = encode_interest(&n, None);
157        let nack_wire = encode_nack(NackReason::Congestion, &interest_wire);
158        let nack = Nack::decode(nack_wire).unwrap();
159        assert_eq!(nack.reason, NackReason::Congestion);
160    }
161
162    // ── NNI encoding ────────────────────────────────────────────────────────
163
164    #[test]
165    fn nni_minimal_encoding() {
166        // 1-byte: 0–255
167        assert_eq!(nni(0), ([0, 0, 0, 0, 0, 0, 0, 0], 1));
168        assert_eq!(nni(255), ([0xFF, 0, 0, 0, 0, 0, 0, 0], 1));
169
170        // 2-byte: 256–65535
171        assert_eq!(nni(256), ([0x01, 0x00, 0, 0, 0, 0, 0, 0], 2));
172        assert_eq!(nni(4000), ([0x0F, 0xA0, 0, 0, 0, 0, 0, 0], 2));
173        assert_eq!(nni(65535), ([0xFF, 0xFF, 0, 0, 0, 0, 0, 0], 2));
174
175        // 4-byte: 65536–4294967295
176        assert_eq!(nni(65536), ([0x00, 0x01, 0x00, 0x00, 0, 0, 0, 0], 4));
177        assert_eq!(nni(1_000_000), ([0x00, 0x0F, 0x42, 0x40, 0, 0, 0, 0], 4));
178
179        // 8-byte: > u32::MAX
180        let big: u64 = 0x1_0000_0000;
181        let (buf, len) = nni(big);
182        assert_eq!(len, 8);
183        assert_eq!(buf, big.to_be_bytes());
184    }
185
186    // ── Wire-format interop tests ───────────────────────────────────────────
187
188    /// Assert two byte slices are equal, hex-formatting on mismatch.
189    pub(super) fn assert_bytes_eq(actual: &[u8], expected: &[u8], msg: &str) {
190        if actual != expected {
191            panic!(
192                "{msg}\n  actual:   {}\n  expected: {}",
193                hex(actual),
194                hex(expected),
195            );
196        }
197    }
198
199    pub(super) fn hex(bytes: &[u8]) -> String {
200        bytes
201            .iter()
202            .map(|b| format!("{b:02X}"))
203            .collect::<Vec<_>>()
204            .join(" ")
205    }
206
207    #[test]
208    fn wire_data_unsigned_structure() {
209        // Data for /A with content "X" and FreshnessPeriod=0.
210        let wire = encode_data_unsigned(&name(&[b"A"]), b"X");
211
212        // 06 (Data) + len
213        assert_eq!(wire[0], 0x06);
214
215        // Name: 07 03 08 01 41
216        assert_bytes_eq(&wire[2..7], &[0x07, 0x03, 0x08, 0x01, 0x41], "Name /A");
217
218        // MetaInfo: 14 03 19 01 00 (FreshnessPeriod=0 as 1-byte NNI)
219        assert_bytes_eq(&wire[7..12], &[0x14, 0x03, 0x19, 0x01, 0x00], "MetaInfo");
220
221        // Content: 15 01 58 ("X")
222        assert_bytes_eq(&wire[12..15], &[0x15, 0x01, 0x58], "Content");
223
224        // SignatureInfo: 16 03 1B 01 00 (DigestSha256)
225        assert_bytes_eq(&wire[15..20], &[0x16, 0x03, 0x1B, 0x01, 0x00], "SigInfo");
226
227        // SignatureValue: 17 20 (32-byte real SHA-256, not zeros)
228        assert_eq!(wire[20], 0x17);
229        assert_eq!(wire[21], 0x20, "SigValue length should be 32");
230        assert!(
231            !wire[22..54].iter().all(|&b| b == 0),
232            "SigValue should be a real SHA-256, not zeros"
233        );
234
235        assert_eq!(wire.len(), 54, "total Data length");
236    }
237
238    #[test]
239    fn wire_nack_reason_nni() {
240        use crate::{Nack, NackReason};
241        let n = name(&[b"A"]);
242        let interest_wire = encode_interest(&n, None);
243        let nack_wire = encode_nack(NackReason::NoRoute, &interest_wire);
244
245        let nack = Nack::decode(nack_wire.clone()).unwrap();
246        assert_eq!(nack.reason, NackReason::NoRoute);
247
248        // Verify the NackReason TLV uses minimal encoding.
249        // 0x0321 as VarNumber: FD 03 21
250        let needle = [0xFD, 0x03, 0x21, 0x01, 0x96];
251        assert!(
252            nack_wire.windows(5).any(|w| w == needle),
253            "NackReason TLV should be FD 03 21 01 96, got: {}",
254            hex(&nack_wire),
255        );
256    }
257
258    #[test]
259    fn wire_ndnd_data_decode() {
260        let ndnd_wire: &[u8] = &[
261            0x06, 0x1D, // Data, length=29
262            0x07, 0x06, // Name, length=6
263            0x08, 0x04, 0x74, 0x65, 0x73, 0x74, // "test"
264            0x14, 0x04, // MetaInfo, length=4
265            0x19, 0x02, 0x27, 0x10, //   FreshnessPeriod=10000
266            0x15, 0x02, 0x68, 0x69, // Content "hi"
267            0x16, 0x03, // SignatureInfo, length=3
268            0x1B, 0x01, 0x00, //   SignatureType=0 (DigestSha256)
269            0x17, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, // SignatureValue (4 bytes)
270        ];
271        let data = Data::decode(Bytes::from_static(ndnd_wire)).unwrap();
272        assert_eq!(data.name.to_string(), "/test");
273        assert_eq!(data.content().map(|b| b.as_ref()), Some(b"hi".as_ref()));
274        let mi = data.meta_info().expect("meta_info");
275        assert_eq!(mi.freshness_period, Some(Duration::from_secs(10)));
276    }
277
278    #[test]
279    fn wire_ndnd_data_no_metainfo_decode() {
280        let ndnd_wire: &[u8] = &[
281            0x06, 0x15, // Data, length=21
282            0x07, 0x06, // Name
283            0x08, 0x04, 0x74, 0x65, 0x73, 0x74, // "test"
284            0x15, 0x02, 0x68, 0x69, // Content "hi"
285            0x16, 0x03, // SignatureInfo
286            0x1B, 0x01, 0x00, //   DigestSha256
287            0x17, 0x04, 0x00, 0x00, 0x00, 0x00, // SignatureValue
288        ];
289        let data = Data::decode(Bytes::from_static(ndnd_wire)).unwrap();
290        assert_eq!(data.name.to_string(), "/test");
291        assert!(data.meta_info().is_none());
292    }
293}