ndn_discovery/hello/
payload.rs

1//! `HelloPayload` — NDN neighbor discovery hello packet TLV codec.
2//!
3//! Both `EtherNeighborDiscovery` and `UdpNeighborDiscovery` encode their
4//! Data Content using this shared format.
5//!
6//! ## Wire format
7//!
8//! ```text
9//! HelloPayload  ::= NODE-NAME TLV
10//!                   (SERVED-PREFIX TLV)*
11//!                   CAPABILITIES TLV?
12//!                   (NEIGHBOR-DIFF TLV)*
13//!
14//! NODE-NAME     ::= 0xC1 length Name
15//! SERVED-PREFIX ::= 0xC2 length Name
16//! CAPABILITIES  ::= 0xC3 length FLAGS-BYTE*
17//! NEIGHBOR-DIFF ::= 0xC4 length (ADD-ENTRY | REMOVE-ENTRY)*
18//! ADD-ENTRY     ::= 0xC5 length Name
19//! REMOVE-ENTRY  ::= 0xC6 length Name
20//! ```
21//!
22//! TLV types use the application-specific range (≥ 0xC0) to avoid collisions
23//! with NDN packet-level types.
24//!
25//! ## Hello Interest / Data names
26//!
27//! ```text
28//! Interest: /ndn/local/nd/hello/<nonce-u32>
29//! Data:     /ndn/local/nd/hello/<nonce-u32>
30//!           Content = HelloPayload TLV (this module)
31//! ```
32//!
33//! The Interest carries no AppParams.  The sender's link-layer address is
34//! obtained from the socket (`recv_with_source`) and never embedded in the
35//! NDN packet.
36
37use bytes::Bytes;
38use ndn_packet::Name;
39use ndn_tlv::{TlvReader, TlvWriter};
40
41use crate::wire::{parse_name_from_tlv, write_name_tlv};
42
43// ─── TLV type constants ───────────────────────────────────────────────────────
44
45/// `NODE-NAME` TLV type: the sender's NDN node name.
46pub const T_NODE_NAME: u64 = 0xC1;
47/// `SERVED-PREFIX` TLV type: a prefix the sender can serve.
48pub const T_SERVED_PREFIX: u64 = 0xC2;
49/// `CAPABILITIES` TLV type: advisory capability flags byte.
50pub const T_CAPABILITIES: u64 = 0xC3;
51/// `NEIGHBOR-DIFF` TLV type: SWIM gossip piggyback.
52pub const T_NEIGHBOR_DIFF: u64 = 0xC4;
53/// `ADD-ENTRY` within a `NEIGHBOR-DIFF`: a newly discovered neighbor.
54pub const T_ADD_ENTRY: u64 = 0xC5;
55/// `REMOVE-ENTRY` within a `NEIGHBOR-DIFF`: a departed neighbor.
56pub const T_REMOVE_ENTRY: u64 = 0xC6;
57/// `PUBLIC-KEY` TLV type: raw 32-byte Ed25519 public key for self-attesting signed hellos.
58pub const T_PUBLIC_KEY: u64 = 0xC8;
59/// `UNICAST-PORT` TLV type: the sender's UDP unicast listen port.
60///
61/// Included in hello Data so that receivers create unicast faces on the
62/// correct port rather than the multicast source port.  Encoded as a
63/// big-endian `u16` (2 bytes).
64pub const T_UNICAST_PORT: u64 = 0xC9;
65
66// ─── Capability flags ─────────────────────────────────────────────────────────
67
68/// Capability flag: this node can reassemble NDN fragments.
69pub const CAP_FRAGMENTATION: u8 = 0x01;
70/// Capability flag: this node has an active content store.
71pub const CAP_CONTENT_STORE: u8 = 0x02;
72/// Capability flag: this node validates signatures on forwarded data.
73pub const CAP_VALIDATION: u8 = 0x04;
74/// Capability flag: this node supports State Vector Sync (SVS).
75pub const CAP_SVS: u8 = 0x08;
76
77// ─── NeighborDiff entry ───────────────────────────────────────────────────────
78
79/// A single entry inside a [`NeighborDiff`]: add or remove a neighbor name.
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub enum DiffEntry {
82    Add(Name),
83    Remove(Name),
84}
85
86/// SWIM gossip piggyback carried in hello Data Content.
87///
88/// Encodes recently discovered and departed neighbors so that other nodes
89/// on the link can update their neighbor tables without waiting for their own
90/// hellos to complete.  Zero additional messages are required — the diff rides
91/// in spare capacity of existing hello traffic.
92#[derive(Clone, Debug, Default)]
93pub struct NeighborDiff {
94    pub entries: Vec<DiffEntry>,
95}
96
97// ─── HelloPayload ─────────────────────────────────────────────────────────────
98
99/// Payload carried in the Content of a hello Data packet.
100#[derive(Clone, Debug)]
101pub struct HelloPayload {
102    /// Mandatory: the sender's NDN node name.
103    pub node_name: Name,
104    /// Prefixes this node produces (for service discovery bootstrapping).
105    pub served_prefixes: Vec<Name>,
106    /// Advisory capability flags (see `CAP_*` constants).
107    pub capabilities: u8,
108    /// SWIM gossip diffs piggybacked on this hello.
109    pub neighbor_diffs: Vec<NeighborDiff>,
110    /// Raw 32-byte Ed25519 public key (self-attesting signed hellos).
111    /// When present, the hello Data is signed by the corresponding private key
112    /// and receivers can verify without any certificate infrastructure.
113    pub public_key: Option<Bytes>,
114    /// UDP unicast listen port.  When present, receivers should create their
115    /// unicast face to `<sender-ip>:<unicast_port>` rather than to the
116    /// source port of the hello packet (which may be the multicast port).
117    pub unicast_port: Option<u16>,
118}
119
120impl HelloPayload {
121    /// Construct a minimal hello payload with just the node name.
122    pub fn new(node_name: Name) -> Self {
123        Self {
124            node_name,
125            served_prefixes: Vec::new(),
126            capabilities: 0,
127            neighbor_diffs: Vec::new(),
128            public_key: None,
129            unicast_port: None,
130        }
131    }
132
133    // ─── Encoding ──────────────────────────────────────────────────────────
134
135    /// Encode the payload to wire bytes (the Content TLV value).
136    pub fn encode(&self) -> Bytes {
137        let mut w = TlvWriter::new();
138        // NODE-NAME
139        w.write_nested(T_NODE_NAME, |w: &mut TlvWriter| {
140            write_name_tlv(w, &self.node_name);
141        });
142        // SERVED-PREFIX (zero or more)
143        for prefix in &self.served_prefixes {
144            w.write_nested(T_SERVED_PREFIX, |w: &mut TlvWriter| {
145                write_name_tlv(w, prefix);
146            });
147        }
148        // CAPABILITIES (omit if zero)
149        if self.capabilities != 0 {
150            w.write_tlv(T_CAPABILITIES, &[self.capabilities]);
151        }
152        // NEIGHBOR-DIFF (zero or more)
153        for diff in &self.neighbor_diffs {
154            w.write_nested(T_NEIGHBOR_DIFF, |w: &mut TlvWriter| {
155                for entry in &diff.entries {
156                    match entry {
157                        DiffEntry::Add(name) => {
158                            w.write_nested(T_ADD_ENTRY, |w: &mut TlvWriter| {
159                                write_name_tlv(w, name);
160                            });
161                        }
162                        DiffEntry::Remove(name) => {
163                            w.write_nested(T_REMOVE_ENTRY, |w: &mut TlvWriter| {
164                                write_name_tlv(w, name);
165                            });
166                        }
167                    }
168                }
169            });
170        }
171        // PUBLIC-KEY (omit if not present)
172        if let Some(ref pk) = self.public_key {
173            w.write_tlv(T_PUBLIC_KEY, pk);
174        }
175        // UNICAST-PORT (omit if not present)
176        if let Some(port) = self.unicast_port {
177            w.write_tlv(T_UNICAST_PORT, &port.to_be_bytes());
178        }
179        w.finish()
180    }
181
182    // ─── Decoding ──────────────────────────────────────────────────────────
183
184    /// Decode a `HelloPayload` from Content bytes.
185    ///
186    /// Returns `None` if the `NODE-NAME` field is missing or malformed;
187    /// unknown TLV types are silently skipped for forward compatibility.
188    pub fn decode(content: &Bytes) -> Option<Self> {
189        let mut r = TlvReader::new(content.clone());
190        let mut node_name: Option<Name> = None;
191        let mut served_prefixes = Vec::new();
192        let mut capabilities: u8 = 0;
193        let mut neighbor_diffs = Vec::new();
194        let mut public_key: Option<Bytes> = None;
195        let mut unicast_port: Option<u16> = None;
196
197        while !r.is_empty() {
198            let (t, v) = r.read_tlv().ok()?;
199            match t {
200                T_NODE_NAME => {
201                    node_name = Some(decode_name_from_nested(&v)?);
202                }
203                T_SERVED_PREFIX => {
204                    if let Some(name) = decode_name_from_nested(&v) {
205                        served_prefixes.push(name);
206                    }
207                }
208                T_CAPABILITIES => {
209                    capabilities = *v.first().unwrap_or(&0);
210                }
211                T_NEIGHBOR_DIFF => {
212                    if let Some(diff) = decode_neighbor_diff(&v) {
213                        neighbor_diffs.push(diff);
214                    }
215                }
216                T_PUBLIC_KEY => {
217                    if v.len() == 32 {
218                        public_key = Some(v);
219                    }
220                }
221                T_UNICAST_PORT => {
222                    if v.len() == 2 {
223                        unicast_port = Some(u16::from_be_bytes([v[0], v[1]]));
224                    }
225                }
226                _ => {} // forward-compatible: skip unknown types
227            }
228        }
229
230        Some(HelloPayload {
231            node_name: node_name?,
232            served_prefixes,
233            capabilities,
234            neighbor_diffs,
235            public_key,
236            unicast_port,
237        })
238    }
239}
240
241// ─── Private decode helpers ───────────────────────────────────────────────────
242
243/// The value of a `NODE-NAME` or `SERVED-PREFIX` TLV is a Name TLV.
244/// This function parses a Name from the nested Name TLV bytes.
245fn decode_name_from_nested(v: &Bytes) -> Option<Name> {
246    parse_name_from_tlv(v)
247}
248
249fn decode_neighbor_diff(v: &Bytes) -> Option<NeighborDiff> {
250    let mut r = TlvReader::new(v.clone());
251    let mut entries = Vec::new();
252    while !r.is_empty() {
253        let (t, val) = r.read_tlv().ok()?;
254        match t {
255            T_ADD_ENTRY => {
256                if let Some(name) = decode_name_from_nested(&val) {
257                    entries.push(DiffEntry::Add(name));
258                }
259            }
260            T_REMOVE_ENTRY => {
261                if let Some(name) = decode_name_from_nested(&val) {
262                    entries.push(DiffEntry::Remove(name));
263                }
264            }
265            _ => {}
266        }
267    }
268    Some(NeighborDiff { entries })
269}
270
271// ─── Tests ────────────────────────────────────────────────────────────────────
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use std::str::FromStr;
277
278    fn name(s: &str) -> Name {
279        Name::from_str(s).unwrap()
280    }
281
282    #[test]
283    fn minimal_roundtrip() {
284        let payload = HelloPayload::new(name("/ndn/test/node"));
285        let wire = payload.encode();
286        let decoded = HelloPayload::decode(&wire).unwrap();
287        assert_eq!(decoded.node_name, name("/ndn/test/node"));
288        assert!(decoded.served_prefixes.is_empty());
289        assert_eq!(decoded.capabilities, 0);
290        assert!(decoded.neighbor_diffs.is_empty());
291    }
292
293    #[test]
294    fn served_prefixes_roundtrip() {
295        let mut payload = HelloPayload::new(name("/ndn/site/router"));
296        payload.served_prefixes.push(name("/ndn/edu/ucla/cs"));
297        payload.served_prefixes.push(name("/ndn/edu/ucla/math"));
298        let wire = payload.encode();
299        let decoded = HelloPayload::decode(&wire).unwrap();
300        assert_eq!(decoded.served_prefixes.len(), 2);
301        assert_eq!(decoded.served_prefixes[0], name("/ndn/edu/ucla/cs"));
302        assert_eq!(decoded.served_prefixes[1], name("/ndn/edu/ucla/math"));
303    }
304
305    #[test]
306    fn capabilities_roundtrip() {
307        let mut payload = HelloPayload::new(name("/ndn/test/node"));
308        payload.capabilities = CAP_CONTENT_STORE | CAP_SVS;
309        let wire = payload.encode();
310        let decoded = HelloPayload::decode(&wire).unwrap();
311        assert_eq!(decoded.capabilities, CAP_CONTENT_STORE | CAP_SVS);
312    }
313
314    #[test]
315    fn capabilities_zero_omitted() {
316        let payload = HelloPayload::new(name("/ndn/test/node"));
317        let wire = payload.encode();
318        // CAPABILITIES TLV should not appear at all when flags == 0.
319        assert!(!wire.windows(1).any(|b| b[0] == T_CAPABILITIES as u8));
320    }
321
322    #[test]
323    fn neighbor_diff_roundtrip() {
324        let mut payload = HelloPayload::new(name("/ndn/test/node"));
325        payload.neighbor_diffs.push(NeighborDiff {
326            entries: vec![
327                DiffEntry::Add(name("/ndn/site/peerA")),
328                DiffEntry::Remove(name("/ndn/site/peerB")),
329            ],
330        });
331        let wire = payload.encode();
332        let decoded = HelloPayload::decode(&wire).unwrap();
333        assert_eq!(decoded.neighbor_diffs.len(), 1);
334        let diff = &decoded.neighbor_diffs[0];
335        assert_eq!(diff.entries.len(), 2);
336        assert_eq!(diff.entries[0], DiffEntry::Add(name("/ndn/site/peerA")));
337        assert_eq!(diff.entries[1], DiffEntry::Remove(name("/ndn/site/peerB")));
338    }
339
340    #[test]
341    fn unknown_tlv_types_skipped() {
342        // Inject an unknown TLV type (0xFF) between known fields.
343        let mut payload = HelloPayload::new(name("/ndn/test/node"));
344        payload.capabilities = CAP_FRAGMENTATION;
345        let mut wire = payload.encode().to_vec();
346        // Append an unknown TLV at the end (0xD0 = 208, valid 1-byte type).
347        wire.extend_from_slice(&[0xD0, 0x02, 0xDE, 0xAD]);
348        let bytes = Bytes::from(wire);
349        // Should decode successfully, ignoring the unknown TLV.
350        let decoded = HelloPayload::decode(&bytes).unwrap();
351        assert_eq!(decoded.node_name, name("/ndn/test/node"));
352        assert_eq!(decoded.capabilities, CAP_FRAGMENTATION);
353    }
354
355    #[test]
356    fn missing_node_name_returns_none() {
357        // Build wire bytes with only a CAPABILITIES field.
358        let mut w = TlvWriter::new();
359        w.write_tlv(T_CAPABILITIES, &[CAP_SVS]);
360        let wire = w.finish();
361        assert!(HelloPayload::decode(&wire).is_none());
362    }
363}