ndn_packet/encode/
interest.rs

1use std::time::Duration;
2
3use bytes::Bytes;
4use ndn_tlv::{TlvReader, TlvWriter};
5
6use super::{next_nonce, nni, rand_nonce_bytes, write_name, write_nni};
7use crate::{Name, SignatureType, tlv_type};
8
9// ──��� Public API ───────────────────────────────────────────────────────────────
10
11/// Encode a minimal Interest TLV.
12///
13/// Includes:
14/// - `Name` built from `name`
15/// - `Nonce` (4 bytes, process-local counter XOR process ID — sufficient for
16///   loop detection; not cryptographically random)
17/// - `InterestLifetime` fixed at 4 000 ms
18/// - `ApplicationParameters` (TLV type 0x24) if `app_params` is `Some`
19///
20/// The returned `Bytes` is a complete, self-contained TLV suitable for direct
21/// transmission over any NDN face.
22pub fn encode_interest(name: &Name, app_params: Option<&[u8]>) -> Bytes {
23    let mut w = TlvWriter::new();
24    w.write_nested(tlv_type::INTEREST, |w| {
25        if let Some(params) = app_params {
26            // Compute ParametersSha256DigestComponent: SHA-256 of the
27            // ApplicationParameters TLV (type + length + value).
28            let mut params_tlv = TlvWriter::new();
29            params_tlv.write_tlv(tlv_type::APP_PARAMETERS, params);
30            let params_wire = params_tlv.finish();
31            let digest = ring::digest::digest(&ring::digest::SHA256, &params_wire);
32
33            // Write Name with ParametersSha256DigestComponent appended.
34            w.write_nested(tlv_type::NAME, |w| {
35                for comp in name.components() {
36                    w.write_tlv(comp.typ, &comp.value);
37                }
38                w.write_tlv(tlv_type::PARAMETERS_SHA256, digest.as_ref());
39            });
40            w.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
41            write_nni(w, tlv_type::INTEREST_LIFETIME, 4000);
42            w.write_tlv(tlv_type::APP_PARAMETERS, params);
43        } else {
44            write_name(w, name);
45            w.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
46            write_nni(w, tlv_type::INTEREST_LIFETIME, 4000);
47        }
48    });
49    w.finish()
50}
51
52/// Ensure an Interest has a Nonce field.
53///
54/// If the Interest wire bytes already contain a Nonce (TLV 0x0A), returns the
55/// bytes unchanged. Otherwise, re-encodes the Interest with a generated Nonce
56/// inserted after the Name.
57///
58/// Per RFC 8569 §4.2, a forwarder MUST add a Nonce before forwarding.
59pub fn ensure_nonce(interest_wire: &Bytes) -> Bytes {
60    // Quick scan: does a Nonce TLV already exist?
61    let mut reader = TlvReader::new(interest_wire.clone());
62    let Ok((typ, value)) = reader.read_tlv() else {
63        return interest_wire.clone();
64    };
65    if typ != tlv_type::INTEREST {
66        return interest_wire.clone();
67    }
68
69    let mut inner = TlvReader::new(value.clone());
70    while !inner.is_empty() {
71        let Ok((t, _)) = inner.read_tlv() else { break };
72        if t == tlv_type::NONCE {
73            return interest_wire.clone(); // already has Nonce
74        }
75    }
76
77    // No Nonce found — re-encode with one inserted.
78    let mut w = TlvWriter::new();
79    w.write_nested(tlv_type::INTEREST, |w| {
80        let mut inner = TlvReader::new(value);
81        let mut name_written = false;
82        while !inner.is_empty() {
83            let Ok((t, v)) = inner.read_tlv() else { break };
84            w.write_tlv(t, &v);
85            // Insert Nonce right after Name (type 0x07).
86            if !name_written && t == tlv_type::NAME {
87                w.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
88                name_written = true;
89            }
90        }
91        if !name_written {
92            // Name wasn't found (malformed), add Nonce at end as fallback.
93            w.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
94        }
95    });
96    w.finish()
97}
98
99// ─── InterestBuilder ─────────────────────────────────────────────────────────
100
101/// Configurable Interest encoder.
102///
103/// ```
104/// # use ndn_packet::encode::InterestBuilder;
105/// # use std::time::Duration;
106/// let wire = InterestBuilder::new("/ndn/test")
107///     .lifetime(Duration::from_millis(2000))
108///     .must_be_fresh()
109///     .build();
110/// ```
111pub struct InterestBuilder {
112    name: Name,
113    lifetime: Option<Duration>,
114    can_be_prefix: bool,
115    must_be_fresh: bool,
116    hop_limit: Option<u8>,
117    app_parameters: Option<Vec<u8>>,
118    forwarding_hint: Option<Vec<Name>>,
119}
120
121impl InterestBuilder {
122    pub fn new(name: impl Into<Name>) -> Self {
123        Self {
124            name: name.into(),
125            lifetime: None,
126            can_be_prefix: false,
127            must_be_fresh: false,
128            hop_limit: None,
129            app_parameters: None,
130            forwarding_hint: None,
131        }
132    }
133
134    pub fn lifetime(mut self, d: Duration) -> Self {
135        self.lifetime = Some(d);
136        self
137    }
138
139    pub fn can_be_prefix(mut self) -> Self {
140        self.can_be_prefix = true;
141        self
142    }
143
144    pub fn must_be_fresh(mut self) -> Self {
145        self.must_be_fresh = true;
146        self
147    }
148
149    pub fn hop_limit(mut self, h: u8) -> Self {
150        self.hop_limit = Some(h);
151        self
152    }
153
154    pub fn app_parameters(mut self, p: impl Into<Vec<u8>>) -> Self {
155        self.app_parameters = Some(p.into());
156        self
157    }
158
159    pub fn forwarding_hint(mut self, names: Vec<Name>) -> Self {
160        self.forwarding_hint = Some(names);
161        self
162    }
163
164    /// Build the Interest wire and return a suitable local receive timeout.
165    ///
166    /// The timeout is the Interest lifetime plus a 500 ms forwarding buffer.
167    /// Use this with `Consumer::fetch_with` so you don't have to compute the
168    /// timeout manually:
169    ///
170    /// ```rust,ignore
171    /// use ndn_packet::encode::InterestBuilder;
172    /// let data = consumer.fetch_with(
173    ///     InterestBuilder::new("/ndn/test")
174    ///         .hop_limit(4)
175    ///         .forwarding_hint(vec!["/hint/hub".parse()?])
176    ///         .app_parameters(b"q=hello"),
177    /// ).await?;
178    /// ```
179    pub fn build_with_timeout(self) -> (Bytes, std::time::Duration) {
180        let lifetime = self
181            .lifetime
182            .unwrap_or(std::time::Duration::from_millis(4000));
183        let timeout = lifetime + std::time::Duration::from_millis(500);
184        (self.build(), timeout)
185    }
186
187    pub fn build(self) -> Bytes {
188        let lifetime_ms = self.lifetime.map(|d| d.as_millis() as u64).unwrap_or(4000);
189
190        let mut w = TlvWriter::new();
191        w.write_nested(tlv_type::INTEREST, |w| {
192            if let Some(ref params) = self.app_parameters {
193                // With ApplicationParameters: append ParametersSha256DigestComponent.
194                let mut params_tlv = TlvWriter::new();
195                params_tlv.write_tlv(tlv_type::APP_PARAMETERS, params);
196                let params_wire = params_tlv.finish();
197                let digest = ring::digest::digest(&ring::digest::SHA256, &params_wire);
198
199                w.write_nested(tlv_type::NAME, |w| {
200                    for comp in self.name.components() {
201                        w.write_tlv(comp.typ, &comp.value);
202                    }
203                    w.write_tlv(tlv_type::PARAMETERS_SHA256, digest.as_ref());
204                });
205            } else {
206                write_name(w, &self.name);
207            }
208            if self.can_be_prefix {
209                w.write_tlv(tlv_type::CAN_BE_PREFIX, &[]);
210            }
211            if self.must_be_fresh {
212                w.write_tlv(tlv_type::MUST_BE_FRESH, &[]);
213            }
214            if let Some(ref hints) = self.forwarding_hint {
215                w.write_nested(tlv_type::FORWARDING_HINT, |w| {
216                    for h in hints {
217                        write_name(w, h);
218                    }
219                });
220            }
221            w.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
222            write_nni(w, tlv_type::INTEREST_LIFETIME, lifetime_ms);
223            if let Some(h) = self.hop_limit {
224                w.write_tlv(tlv_type::HOP_LIMIT, &[h]);
225            }
226            if let Some(ref params) = self.app_parameters {
227                w.write_tlv(tlv_type::APP_PARAMETERS, params);
228            }
229        });
230        w.finish()
231    }
232
233    /// Encode and sign the Interest packet (Signed Interest per NDN v0.3 §5.4).
234    ///
235    /// `sig_type` and `key_locator` describe the signature algorithm and
236    /// optional KeyLocator name for InterestSignatureInfo. `sign_fn` receives
237    /// the signed region (Name through InterestSignatureInfo) and returns the
238    /// raw signature bytes.
239    ///
240    /// If `app_parameters` was not set, an empty ApplicationParameters TLV is
241    /// used — signed Interests must carry ApplicationParameters per spec.
242    ///
243    /// Anti-replay fields (SignatureNonce, SignatureTime, SignatureSeqNum) are
244    /// included in InterestSignatureInfo if set via the builder. If none are
245    /// set, a random 8-byte SignatureNonce and the current wall-clock
246    /// SignatureTime are generated automatically.
247    pub async fn sign<F, Fut>(
248        self,
249        sig_type: SignatureType,
250        key_locator: Option<&Name>,
251        sign_fn: F,
252    ) -> Bytes
253    where
254        F: FnOnce(&[u8]) -> Fut,
255        Fut: std::future::Future<Output = Bytes>,
256    {
257        let (mut signed_region, digest_value_offset, app_params_offset) =
258            self.build_signed_interest_region(sig_type, key_locator);
259        let sig_value = sign_fn(&signed_region).await;
260
261        // Build the InterestSignatureValue TLV.
262        let mut sigval_w = TlvWriter::new();
263        sigval_w.write_tlv(tlv_type::INTEREST_SIGNATURE_VALUE, &sig_value);
264        let sigval_bytes = sigval_w.finish();
265
266        // Compute the actual ParametersSha256DigestComponent value.
267        let mut digest_input =
268            Vec::with_capacity((signed_region.len() - app_params_offset) + sigval_bytes.len());
269        digest_input.extend_from_slice(&signed_region[app_params_offset..]);
270        digest_input.extend_from_slice(&sigval_bytes);
271        let actual_digest = ring::digest::digest(&ring::digest::SHA256, &digest_input);
272
273        // Patch the placeholder with the actual digest.
274        signed_region[digest_value_offset..digest_value_offset + 32]
275            .copy_from_slice(actual_digest.as_ref());
276
277        let inner_len = signed_region.len() + sigval_bytes.len();
278        let mut outer = TlvWriter::with_capacity(inner_len + 10);
279        outer.write_varu64(tlv_type::INTEREST);
280        outer.write_varu64(inner_len as u64);
281        outer.write_raw(&signed_region);
282        outer.write_raw(&sigval_bytes);
283        outer.finish()
284    }
285
286    /// Sign with `DigestSha256` — SHA-256 of the signed region.
287    ///
288    /// This is the minimum signature type accepted by NFD for management
289    /// Interests on the loopback face.  No key material is required; NFD
290    /// verifies the signature by recomputing the SHA-256 itself.
291    ///
292    /// Use this when sending management commands (`rib/register`, etc.) to
293    /// NFD or ndnd so they do not silently drop the Interest.
294    #[cfg(feature = "std")]
295    pub fn sign_digest_sha256(self) -> Bytes {
296        self.sign_sync(SignatureType::DigestSha256, None, |region| {
297            let digest = ring::digest::digest(&ring::digest::SHA256, region);
298            Bytes::copy_from_slice(digest.as_ref())
299        })
300    }
301
302    /// Synchronous encode-and-sign for CPU-only signers (Ed25519, HMAC).
303    pub fn sign_sync<F>(
304        self,
305        sig_type: SignatureType,
306        key_locator: Option<&Name>,
307        sign_fn: F,
308    ) -> Bytes
309    where
310        F: FnOnce(&[u8]) -> Bytes,
311    {
312        let (mut signed_region, digest_value_offset, app_params_offset) =
313            self.build_signed_interest_region(sig_type, key_locator);
314        let sig_value = sign_fn(&signed_region);
315
316        // Build the InterestSignatureValue TLV.
317        let mut sigval_w = TlvWriter::new();
318        sigval_w.write_tlv(tlv_type::INTEREST_SIGNATURE_VALUE, &sig_value);
319        let sigval_bytes = sigval_w.finish();
320
321        // Compute the actual ParametersSha256DigestComponent value:
322        // SHA-256 over ApplicationParameters + InterestSignatureInfo +
323        // InterestSignatureValue TLVs (NDN Packet Format v0.3 §5.4).
324        let mut digest_input =
325            Vec::with_capacity((signed_region.len() - app_params_offset) + sigval_bytes.len());
326        digest_input.extend_from_slice(&signed_region[app_params_offset..]);
327        digest_input.extend_from_slice(&sigval_bytes);
328        let actual_digest = ring::digest::digest(&ring::digest::SHA256, &digest_input);
329
330        // Patch the 32-byte placeholder in the Name with the actual digest.
331        signed_region[digest_value_offset..digest_value_offset + 32]
332            .copy_from_slice(actual_digest.as_ref());
333
334        let inner_len = signed_region.len() + sigval_bytes.len();
335        let mut outer = TlvWriter::with_capacity(inner_len + 10);
336        outer.write_varu64(tlv_type::INTEREST);
337        outer.write_varu64(inner_len as u64);
338        outer.write_raw(&signed_region);
339        outer.write_raw(&sigval_bytes);
340        outer.finish()
341    }
342
343    /// Build the signed region for a Signed Interest: Name (with a 32-byte
344    /// placeholder for ParametersSha256DigestComponent) through
345    /// InterestSignatureInfo, including all fields in between.
346    ///
347    /// Returns `(bytes, digest_value_offset, app_params_offset)`:
348    /// - `bytes`: the full signed region
349    /// - `digest_value_offset`: byte offset of the 32-byte placeholder that
350    ///   must be replaced with the actual SHA-256 digest after signing
351    /// - `app_params_offset`: byte offset where ApplicationParameters TLV
352    ///   starts — used as the beginning of the digest-coverage region
353    ///
354    /// The caller computes the actual ParametersSha256DigestComponent value as
355    /// SHA-256 of `bytes[app_params_offset..]` (ApplicationParameters +
356    /// InterestSignatureInfo) concatenated with the InterestSignatureValue TLV,
357    /// then patches `bytes[digest_value_offset..digest_value_offset+32]`.
358    fn build_signed_interest_region(
359        self,
360        sig_type: SignatureType,
361        key_locator: Option<&Name>,
362    ) -> (Vec<u8>, usize, usize) {
363        let params = self.app_parameters.unwrap_or_default();
364        let lifetime_ms = self.lifetime.map(|d| d.as_millis() as u64).unwrap_or(4000);
365
366        let mut inner = TlvWriter::new();
367
368        // Name with a 32-byte placeholder for ParametersSha256DigestComponent.
369        // The actual digest is computed after the signature is known and patched
370        // in by the caller.
371        inner.write_nested(tlv_type::NAME, |w| {
372            for comp in self.name.components() {
373                w.write_tlv(comp.typ, &comp.value);
374            }
375            w.write_tlv(tlv_type::PARAMETERS_SHA256, &[0u8; 32]);
376        });
377        // The placeholder is the last 32 bytes of the Name TLV.
378        let digest_value_offset = inner.len() - 32;
379
380        // Selectors.
381        if self.can_be_prefix {
382            inner.write_tlv(tlv_type::CAN_BE_PREFIX, &[]);
383        }
384        if self.must_be_fresh {
385            inner.write_tlv(tlv_type::MUST_BE_FRESH, &[]);
386        }
387
388        // ForwardingHint.
389        if let Some(ref hints) = self.forwarding_hint {
390            inner.write_nested(tlv_type::FORWARDING_HINT, |w| {
391                for h in hints {
392                    write_name(w, h);
393                }
394            });
395        }
396
397        // Nonce, Lifetime, HopLimit.
398        inner.write_tlv(tlv_type::NONCE, &next_nonce().to_be_bytes());
399        write_nni(&mut inner, tlv_type::INTEREST_LIFETIME, lifetime_ms);
400        if let Some(h) = self.hop_limit {
401            inner.write_tlv(tlv_type::HOP_LIMIT, &[h]);
402        }
403
404        // Track start of ApplicationParameters TLV for digest-coverage.
405        let app_params_offset = inner.len();
406
407        // ApplicationParameters.
408        inner.write_tlv(tlv_type::APP_PARAMETERS, &params);
409
410        // InterestSignatureInfo with anti-replay fields.
411        inner.write_nested(tlv_type::INTEREST_SIGNATURE_INFO, |w| {
412            write_nni(w, tlv_type::SIGNATURE_TYPE, sig_type.code());
413            if let Some(kl) = key_locator {
414                w.write_nested(tlv_type::KEY_LOCATOR, |w| {
415                    write_name(w, kl);
416                });
417            }
418            // Auto-generate SignatureNonce (8 random bytes) and SignatureTime
419            // (current wall clock) for replay protection.
420            let nonce_bytes: [u8; 8] = rand_nonce_bytes();
421            w.write_tlv(tlv_type::SIGNATURE_NONCE, &nonce_bytes);
422            let now_ms = std::time::SystemTime::now()
423                .duration_since(std::time::UNIX_EPOCH)
424                .unwrap_or_default()
425                .as_millis() as u64;
426            let (time_buf, time_len) = nni(now_ms);
427            w.write_tlv(tlv_type::SIGNATURE_TIME, &time_buf[..time_len]);
428        });
429
430        (
431            inner.finish().to_vec(),
432            digest_value_offset,
433            app_params_offset,
434        )
435    }
436}
437
438/// Allow `&str` and `String` to convert into `Name` for builder ergonomics.
439impl From<&str> for Name {
440    fn from(s: &str) -> Self {
441        s.parse().unwrap_or_else(|_| Name::root())
442    }
443}
444
445impl From<String> for Name {
446    fn from(s: String) -> Self {
447        s.parse().unwrap_or_else(|_| Name::root())
448    }
449}
450
451// ─── Tests ────────────────────────────────────────────────────────────────────
452
453#[cfg(test)]
454mod tests {
455    use super::super::tests::{assert_bytes_eq, name};
456    use super::*;
457    use crate::Interest;
458    use bytes::Bytes;
459    use std::time::Duration;
460
461    #[test]
462    fn interest_roundtrip_name() {
463        let n = name(&[b"localhost", b"ndn-ctl", b"get-stats"]);
464        let bytes = encode_interest(&n, None);
465        let interest = Interest::decode(bytes).unwrap();
466        assert_eq!(*interest.name, n);
467    }
468
469    #[test]
470    fn interest_with_app_params_roundtrip() {
471        let n = name(&[b"localhost", b"ndn-ctl", b"add-route"]);
472        let params = br#"{"cmd":"add_route","prefix":"/ndn","face":1,"cost":10}"#;
473        let bytes = encode_interest(&n, Some(params));
474        let interest = Interest::decode(bytes).unwrap();
475        // Name has the original components plus ParametersSha256DigestComponent.
476        assert_eq!(interest.name.len(), n.len() + 1);
477        for (i, comp) in n.components().iter().enumerate() {
478            assert_eq!(interest.name.components()[i], *comp);
479        }
480        // Last component is the digest (type 0x02, 32 bytes).
481        let last = &interest.name.components()[n.len()];
482        assert_eq!(last.typ, tlv_type::PARAMETERS_SHA256);
483        assert_eq!(last.value.len(), 32);
484        assert_eq!(
485            interest.app_parameters().map(|b| b.as_ref()),
486            Some(params.as_ref())
487        );
488    }
489
490    #[test]
491    fn interest_has_nonce_and_lifetime() {
492        let n = name(&[b"test"]);
493        let bytes = encode_interest(&n, None);
494        let interest = Interest::decode(bytes).unwrap();
495        assert!(interest.nonce().is_some());
496        assert_eq!(interest.lifetime(), Some(Duration::from_millis(4000)));
497    }
498
499    #[test]
500    fn ensure_nonce_adds_when_missing() {
501        let n = name(&[b"test"]);
502        let mut w = TlvWriter::new();
503        w.write_nested(tlv_type::INTEREST, |w| {
504            super::write_name(w, &n);
505            w.write_tlv(tlv_type::INTEREST_LIFETIME, &4000u64.to_be_bytes());
506        });
507        let no_nonce = w.finish();
508        let interest = Interest::decode(no_nonce.clone()).unwrap();
509        assert!(interest.nonce().is_none());
510
511        let with_nonce = ensure_nonce(&no_nonce);
512        let interest2 = Interest::decode(with_nonce).unwrap();
513        assert!(interest2.nonce().is_some());
514    }
515
516    #[test]
517    fn ensure_nonce_preserves_existing() {
518        let n = name(&[b"test"]);
519        let bytes = encode_interest(&n, None);
520        let original_nonce = Interest::decode(bytes.clone()).unwrap().nonce();
521        let result = ensure_nonce(&bytes);
522        assert_eq!(result, bytes); // unchanged
523        let after = Interest::decode(result).unwrap().nonce();
524        assert_eq!(original_nonce, after);
525    }
526
527    #[test]
528    fn nonces_are_unique() {
529        let n = name(&[b"test"]);
530        let b1 = encode_interest(&n, None);
531        let b2 = encode_interest(&n, None);
532        let i1 = Interest::decode(b1).unwrap();
533        let i2 = Interest::decode(b2).unwrap();
534        assert_ne!(i1.nonce(), i2.nonce());
535    }
536
537    // ── InterestBuilder ──────────────────────────────────────────────────────
538
539    #[test]
540    fn interest_builder_basic() {
541        let wire = InterestBuilder::new("/ndn/test").build();
542        let interest = Interest::decode(wire).unwrap();
543        assert_eq!(interest.name.to_string(), "/ndn/test");
544        assert!(interest.nonce().is_some());
545        assert_eq!(interest.lifetime(), Some(Duration::from_millis(4000)));
546    }
547
548    #[test]
549    fn interest_builder_custom_lifetime() {
550        let wire = InterestBuilder::new("/test")
551            .lifetime(Duration::from_millis(2000))
552            .build();
553        let interest = Interest::decode(wire).unwrap();
554        assert_eq!(interest.lifetime(), Some(Duration::from_millis(2000)));
555    }
556
557    #[test]
558    fn interest_builder_from_str() {
559        let wire = InterestBuilder::new("/a/b/c").build();
560        let interest = Interest::decode(wire).unwrap();
561        assert_eq!(interest.name.len(), 3);
562    }
563
564    #[test]
565    fn interest_builder_app_params_preserves_selectors() {
566        let wire = InterestBuilder::new("/cmd")
567            .can_be_prefix()
568            .must_be_fresh()
569            .lifetime(Duration::from_millis(2000))
570            .hop_limit(64)
571            .app_parameters(b"payload".to_vec())
572            .build();
573        let interest = Interest::decode(wire).unwrap();
574        assert!(interest.selectors().can_be_prefix);
575        assert!(interest.selectors().must_be_fresh);
576        assert_eq!(interest.lifetime(), Some(Duration::from_millis(2000)));
577        assert_eq!(interest.hop_limit(), Some(64));
578        assert_eq!(
579            interest.app_parameters().map(|b| b.as_ref()),
580            Some(b"payload".as_ref())
581        );
582    }
583
584    #[test]
585    fn interest_builder_forwarding_hint() {
586        let hint: Name = "/ndn/gateway".parse().unwrap();
587        let wire = InterestBuilder::new("/test")
588            .forwarding_hint(vec![hint])
589            .build();
590        let interest = Interest::decode(wire).unwrap();
591        let hints = interest.forwarding_hint().expect("forwarding_hint present");
592        assert_eq!(hints.len(), 1);
593        assert_eq!(hints[0].to_string(), "/ndn/gateway");
594    }
595
596    #[test]
597    fn interest_builder_sign_sync_roundtrip() {
598        let key_name: Name = "/key/test".parse().unwrap();
599        let wire = InterestBuilder::new("/signed/cmd")
600            .app_parameters(b"params".to_vec())
601            .sign_sync(
602                crate::SignatureType::SignatureEd25519,
603                Some(&key_name),
604                |region| {
605                    let digest = ring::digest::digest(&ring::digest::SHA256, region);
606                    Bytes::copy_from_slice(digest.as_ref())
607                },
608            );
609        let interest = Interest::decode(wire).unwrap();
610        assert_eq!(interest.name.components()[0].value.as_ref(), b"signed");
611        assert_eq!(interest.name.components()[1].value.as_ref(), b"cmd");
612        let last = interest.name.components().last().unwrap();
613        assert_eq!(last.typ, tlv_type::PARAMETERS_SHA256);
614        assert_eq!(last.value.len(), 32);
615        let si = interest.sig_info().expect("sig_info present");
616        assert_eq!(si.sig_type, crate::SignatureType::SignatureEd25519);
617        let kl = si.key_locator.as_ref().expect("key locator present");
618        assert_eq!(kl.to_string(), "/key/test");
619        assert!(interest.sig_value().is_some());
620        assert_eq!(
621            interest.app_parameters().map(|b| b.as_ref()),
622            Some(b"params".as_ref())
623        );
624    }
625
626    #[test]
627    fn interest_builder_sign_sync_auto_anti_replay() {
628        let wire = InterestBuilder::new("/cmd").sign_sync(
629            crate::SignatureType::SignatureEd25519,
630            None,
631            |region| {
632                Bytes::copy_from_slice(ring::digest::digest(&ring::digest::SHA256, region).as_ref())
633            },
634        );
635        let interest = Interest::decode(wire).unwrap();
636        let si = interest.sig_info().expect("sig_info");
637        assert!(si.sig_nonce.is_some());
638        assert!(si.sig_time.is_some());
639    }
640
641    #[test]
642    fn interest_builder_sign_sync_empty_params_default() {
643        let wire = InterestBuilder::new("/cmd").sign_sync(
644            crate::SignatureType::DigestSha256,
645            None,
646            |region| {
647                let d = ring::digest::digest(&ring::digest::SHA256, region);
648                Bytes::copy_from_slice(d.as_ref())
649            },
650        );
651        let interest = Interest::decode(wire).unwrap();
652        let ap = interest.app_parameters().expect("app_params present");
653        assert!(ap.is_empty());
654    }
655
656    #[test]
657    fn interest_builder_sign_sync_signed_region() {
658        let wire = InterestBuilder::new("/test")
659            .app_parameters(b"data".to_vec())
660            .sign_sync(crate::SignatureType::SignatureEd25519, None, |region| {
661                assert_eq!(region[0], tlv_type::NAME as u8);
662                Bytes::copy_from_slice(ring::digest::digest(&ring::digest::SHA256, region).as_ref())
663            });
664        let interest = Interest::decode(wire.clone()).unwrap();
665        let region = interest.signed_region().expect("signed region present");
666        assert_eq!(region[0], tlv_type::NAME as u8);
667        assert!(interest.sig_value().is_some());
668    }
669
670    #[test]
671    fn interest_builder_sign_async_matches_sync_structure() {
672        use std::pin::pin;
673        use std::task::{Context, Wake, Waker};
674
675        struct NoopWaker;
676        impl Wake for NoopWaker {
677            fn wake(self: std::sync::Arc<Self>) {}
678        }
679        let waker = Waker::from(std::sync::Arc::new(NoopWaker));
680        let mut cx = Context::from_waker(&waker);
681
682        let fut = InterestBuilder::new("/test")
683            .app_parameters(b"p".to_vec())
684            .sign(
685                crate::SignatureType::SignatureEd25519,
686                None,
687                |region: &[u8]| {
688                    let d = ring::digest::digest(&ring::digest::SHA256, region);
689                    std::future::ready(Bytes::copy_from_slice(d.as_ref()))
690                },
691            );
692        let mut fut = pin!(fut);
693        let async_wire = match fut.as_mut().poll(&mut cx) {
694            std::task::Poll::Ready(b) => b,
695            std::task::Poll::Pending => panic!("should complete immediately"),
696        };
697
698        let async_i = Interest::decode(async_wire).unwrap();
699        assert!(async_i.sig_info().is_some());
700        assert!(async_i.sig_value().is_some());
701        assert!(async_i.signed_region().is_some());
702
703        let sync_wire = InterestBuilder::new("/test")
704            .app_parameters(b"p".to_vec())
705            .sign_sync(crate::SignatureType::SignatureEd25519, None, |region| {
706                Bytes::copy_from_slice(ring::digest::digest(&ring::digest::SHA256, region).as_ref())
707            });
708        let sync_i = Interest::decode(sync_wire).unwrap();
709        assert!(sync_i.sig_info().is_some());
710        assert!(sync_i.sig_value().is_some());
711    }
712
713    #[test]
714    fn interest_builder_sign_sync_with_all_options() {
715        let hint: Name = "/ndn/relay".parse().unwrap();
716        let key_name: Name = "/my/key".parse().unwrap();
717        let wire = InterestBuilder::new("/prefix/command")
718            .can_be_prefix()
719            .must_be_fresh()
720            .lifetime(Duration::from_millis(8000))
721            .hop_limit(32)
722            .forwarding_hint(vec![hint])
723            .app_parameters(b"payload".to_vec())
724            .sign_sync(
725                crate::SignatureType::SignatureEd25519,
726                Some(&key_name),
727                |region| {
728                    Bytes::copy_from_slice(
729                        ring::digest::digest(&ring::digest::SHA256, region).as_ref(),
730                    )
731                },
732            );
733        let i = Interest::decode(wire).unwrap();
734        assert!(i.selectors().can_be_prefix);
735        assert!(i.selectors().must_be_fresh);
736        assert_eq!(i.lifetime(), Some(Duration::from_millis(8000)));
737        assert_eq!(i.hop_limit(), Some(32));
738        let hints = i.forwarding_hint().expect("forwarding_hint");
739        assert_eq!(hints[0].to_string(), "/ndn/relay");
740        assert_eq!(
741            i.app_parameters().map(|b| b.as_ref()),
742            Some(b"payload".as_ref())
743        );
744        let si = i.sig_info().expect("sig_info");
745        assert_eq!(si.sig_type, crate::SignatureType::SignatureEd25519);
746        assert_eq!(si.key_locator.as_ref().unwrap().to_string(), "/my/key");
747        assert!(i.sig_value().is_some());
748        assert!(i.signed_region().is_some());
749    }
750
751    // ── Wire-format tests ────────────────────────────────────────────────────
752
753    #[test]
754    fn wire_interest_nni_lifetime() {
755        let wire = encode_interest(&name(&[b"ndn", b"edu"]), None);
756
757        let pos = wire
758            .windows(2)
759            .position(|w| w == [0x0C, 0x02])
760            .expect("InterestLifetime should be type=0x0C len=0x02 (2 bytes)");
761        assert_bytes_eq(
762            &wire[pos..pos + 4],
763            &[0x0C, 0x02, 0x0F, 0xA0],
764            "InterestLifetime 4000ms",
765        );
766    }
767
768    #[test]
769    fn wire_interest_structure() {
770        let wire = encode_interest(&name(&[b"A"]), None);
771
772        assert_eq!(wire[0], 0x05, "outer type must be Interest (0x05)");
773
774        let name_expected = [0x07, 0x03, 0x08, 0x01, 0x41];
775        assert_bytes_eq(&wire[2..7], &name_expected, "Name /A");
776
777        assert_eq!(wire[7], 0x0A, "Nonce type");
778        assert_eq!(wire[8], 0x04, "Nonce length");
779
780        assert_bytes_eq(&wire[13..17], &[0x0C, 0x02, 0x0F, 0xA0], "Lifetime");
781
782        assert_eq!(wire.len(), 17, "total Interest length");
783    }
784
785    #[test]
786    fn wire_interest_builder_selectors() {
787        let wire = InterestBuilder::new("/A")
788            .can_be_prefix()
789            .must_be_fresh()
790            .lifetime(Duration::from_millis(1000))
791            .build();
792
793        let after_name = 7;
794        assert_bytes_eq(
795            &wire[after_name..after_name + 2],
796            &[0x21, 0x00],
797            "CanBePrefix",
798        );
799        assert_bytes_eq(
800            &wire[after_name + 2..after_name + 4],
801            &[0x12, 0x00],
802            "MustBeFresh",
803        );
804        assert_eq!(wire[after_name + 4], 0x0A, "Nonce type");
805        let lt_pos = after_name + 4 + 6;
806        assert_bytes_eq(
807            &wire[lt_pos..lt_pos + 4],
808            &[0x0C, 0x02, 0x03, 0xE8],
809            "Lifetime 1000ms",
810        );
811    }
812
813    #[test]
814    fn wire_ndnd_interest_decode() {
815        let ndnd_wire: &[u8] = &[
816            0x05, 0x16, 0x07, 0x0A, 0x08, 0x03, 0x6E, 0x64, 0x6E, 0x08, 0x03, 0x65, 0x64, 0x75,
817            0x0A, 0x04, 0x01, 0x02, 0x03, 0x04, 0x0C, 0x02, 0x0F, 0xA0,
818        ];
819        let interest = Interest::decode(Bytes::from_static(ndnd_wire)).unwrap();
820        assert_eq!(interest.name.to_string(), "/ndn/edu");
821        assert_eq!(interest.nonce(), Some(0x01020304));
822        assert_eq!(interest.lifetime(), Some(Duration::from_millis(4000)));
823    }
824
825    #[test]
826    fn wire_ndnd_interest_1byte_lifetime_decode() {
827        let ndnd_wire: &[u8] = &[
828            0x05, 0x15, 0x07, 0x0A, 0x08, 0x03, 0x6E, 0x64, 0x6E, 0x08, 0x03, 0x65, 0x64, 0x75,
829            0x0A, 0x04, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x01, 0x64,
830        ];
831        let interest = Interest::decode(Bytes::from_static(ndnd_wire)).unwrap();
832        assert_eq!(interest.lifetime(), Some(Duration::from_millis(100)));
833    }
834
835    #[test]
836    fn wire_ndnd_interest_4byte_lifetime_decode() {
837        let ndnd_wire: &[u8] = &[
838            0x05, 0x18, 0x07, 0x0A, 0x08, 0x03, 0x6E, 0x64, 0x6E, 0x08, 0x03, 0x65, 0x64, 0x75,
839            0x0A, 0x04, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x04, 0x00, 0x01, 0x86, 0xA0,
840        ];
841        let interest = Interest::decode(Bytes::from_static(ndnd_wire)).unwrap();
842        assert_eq!(interest.lifetime(), Some(Duration::from_millis(100000)));
843    }
844}