ndn_config/
nfd_dataset.rs

1/// NFD Management Protocol — Status Dataset TLV encoding and decoding.
2///
3/// Status datasets are returned for list queries (`faces/list`, `fib/list`,
4/// `rib/list`, `strategy-choice/list`).  Each dataset is a concatenation of
5/// repeated TLV blocks (type 0x80) inside a Data content field.
6///
7/// Wire format follows the NFD Management Protocol specification:
8/// <https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt>
9/// <https://redmine.named-data.net/projects/nfd/wiki/FibMgmt>
10/// <https://redmine.named-data.net/projects/nfd/wiki/RibMgmt>
11/// <https://redmine.named-data.net/projects/nfd/wiki/StrategyChoice>
12use bytes::{Bytes, BytesMut};
13use ndn_packet::{Name, NameComponent};
14use ndn_tlv::{TlvReader, TlvWriter};
15
16// ─── TLV type constants ──────────────────────────────────────────────────────
17
18mod tlv {
19    // Shared across dataset types (also overlap with ControlParameters fields).
20    pub const FACE_ID: u64 = 0x69;
21    pub const COST: u64 = 0x6a;
22    pub const STRATEGY_WRAPPER: u64 = 0x6b;
23    pub const FLAGS: u64 = 0x6c;
24    pub const EXPIRATION_PERIOD: u64 = 0x6d;
25    pub const ORIGIN: u64 = 0x6f;
26    pub const URI: u64 = 0x72;
27
28    // FaceStatus
29    pub const FACE_STATUS: u64 = 0x80;
30    pub const LOCAL_URI: u64 = 0x81;
31    pub const FACE_SCOPE: u64 = 0x84;
32    pub const FACE_PERSISTENCY: u64 = 0x85;
33    pub const LINK_TYPE: u64 = 0x86;
34    pub const BASE_CONGESTION_MARKING_INTERVAL: u64 = 0x87;
35    pub const DEFAULT_CONGESTION_THRESHOLD: u64 = 0x88;
36    pub const MTU: u64 = 0x89;
37    pub const N_IN_INTERESTS: u64 = 0x90;
38    pub const N_IN_DATA: u64 = 0x91;
39    pub const N_OUT_INTERESTS: u64 = 0x92;
40    pub const N_OUT_DATA: u64 = 0x93;
41    pub const N_IN_BYTES: u64 = 0x94;
42    pub const N_OUT_BYTES: u64 = 0x95;
43    pub const N_IN_NACKS: u64 = 0x97;
44    pub const N_OUT_NACKS: u64 = 0x98;
45
46    // FibEntry / RibEntry outer container
47    pub const ENTRY: u64 = 0x80;
48    // FibEntry NextHopRecord
49    pub const NEXT_HOP_RECORD: u64 = 0x81;
50    // RibEntry Route
51    pub const ROUTE: u64 = 0x81;
52
53    // Standard NDN Name type.
54    pub const NAME: u64 = 0x07;
55    #[allow(dead_code)]
56    pub const NAME_COMPONENT: u64 = 0x08;
57}
58
59// ─── NonNegativeInteger helpers ──────────────────────────────────────────────
60
61/// Encode a NonNegativeInteger: the shortest big-endian representation.
62fn encode_non_neg_int(value: u64) -> Vec<u8> {
63    if value <= 0xFF {
64        vec![value as u8]
65    } else if value <= 0xFFFF {
66        (value as u16).to_be_bytes().to_vec()
67    } else if value <= 0xFFFF_FFFF {
68        (value as u32).to_be_bytes().to_vec()
69    } else {
70        value.to_be_bytes().to_vec()
71    }
72}
73
74/// Write a TLV element with a NonNegativeInteger value.
75fn write_non_neg_int(w: &mut TlvWriter, typ: u64, value: u64) {
76    w.write_tlv(typ, &encode_non_neg_int(value));
77}
78
79/// Read a NonNegativeInteger from a value slice (1, 2, 4, or 8 bytes).
80fn read_non_neg_int(buf: &[u8]) -> Option<u64> {
81    match buf.len() {
82        1 => Some(buf[0] as u64),
83        2 => Some(u16::from_be_bytes([buf[0], buf[1]]) as u64),
84        4 => Some(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as u64),
85        8 => Some(u64::from_be_bytes([
86            buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
87        ])),
88        _ => None,
89    }
90}
91
92// ─── Name helpers ─────────────────────────────────────────────────────────────
93
94fn encode_name(w: &mut TlvWriter, name: &Name) {
95    w.write_nested(tlv::NAME, |w| {
96        for comp in name.components() {
97            w.write_tlv(comp.typ, &comp.value);
98        }
99    });
100}
101
102fn decode_name(value: Bytes) -> Option<Name> {
103    let mut r = TlvReader::new(value);
104    let mut components = Vec::new();
105    while !r.is_empty() {
106        let (typ, val) = r.read_tlv().ok()?;
107        components.push(NameComponent { typ, value: val });
108    }
109    if components.is_empty() {
110        Some(Name::root())
111    } else {
112        Some(Name::from_components(components))
113    }
114}
115
116// ─── FaceStatus ───────────────────────────────────────────────────────────────
117
118/// NFD FaceStatus dataset entry (TLV type 0x80).
119///
120/// Returned by `faces/list`.
121#[derive(Debug, Clone)]
122pub struct FaceStatus {
123    pub face_id: u64,
124    /// Remote URI (e.g. `udp4://192.168.1.1:6363`)
125    pub uri: String,
126    /// Local URI (e.g. `udp4://0.0.0.0:6363`)
127    pub local_uri: String,
128    /// 0 = non-local, 1 = local
129    pub face_scope: u64,
130    /// 0 = persistent, 1 = on-demand, 2 = permanent
131    pub face_persistency: u64,
132    /// 0 = point-to-point, 1 = multi-access
133    pub link_type: u64,
134    pub mtu: Option<u64>,
135    pub base_congestion_marking_interval: Option<u64>,
136    pub default_congestion_threshold: Option<u64>,
137    pub n_in_interests: u64,
138    pub n_in_data: u64,
139    pub n_in_nacks: u64,
140    pub n_out_interests: u64,
141    pub n_out_data: u64,
142    pub n_out_nacks: u64,
143    pub n_in_bytes: u64,
144    pub n_out_bytes: u64,
145}
146
147impl FaceStatus {
148    /// Encode to a complete FaceStatus TLV block (type 0x80).
149    pub fn encode(&self) -> Bytes {
150        let mut w = TlvWriter::new();
151        w.write_nested(tlv::FACE_STATUS, |w| {
152            write_non_neg_int(w, tlv::FACE_ID, self.face_id);
153            w.write_tlv(tlv::URI, self.uri.as_bytes());
154            w.write_tlv(tlv::LOCAL_URI, self.local_uri.as_bytes());
155            write_non_neg_int(w, tlv::FACE_SCOPE, self.face_scope);
156            write_non_neg_int(w, tlv::FACE_PERSISTENCY, self.face_persistency);
157            write_non_neg_int(w, tlv::LINK_TYPE, self.link_type);
158            if let Some(v) = self.base_congestion_marking_interval {
159                write_non_neg_int(w, tlv::BASE_CONGESTION_MARKING_INTERVAL, v);
160            }
161            if let Some(v) = self.default_congestion_threshold {
162                write_non_neg_int(w, tlv::DEFAULT_CONGESTION_THRESHOLD, v);
163            }
164            if let Some(v) = self.mtu {
165                write_non_neg_int(w, tlv::MTU, v);
166            }
167            write_non_neg_int(w, tlv::N_IN_INTERESTS, self.n_in_interests);
168            write_non_neg_int(w, tlv::N_IN_DATA, self.n_in_data);
169            write_non_neg_int(w, tlv::N_IN_NACKS, self.n_in_nacks);
170            write_non_neg_int(w, tlv::N_OUT_INTERESTS, self.n_out_interests);
171            write_non_neg_int(w, tlv::N_OUT_DATA, self.n_out_data);
172            write_non_neg_int(w, tlv::N_OUT_NACKS, self.n_out_nacks);
173            write_non_neg_int(w, tlv::N_IN_BYTES, self.n_in_bytes);
174            write_non_neg_int(w, tlv::N_OUT_BYTES, self.n_out_bytes);
175        });
176        w.finish()
177    }
178
179    /// Decode one FaceStatus entry from the front of `buf`, advancing the cursor.
180    pub fn decode(buf: &mut &[u8]) -> Option<Self> {
181        let mut r = TlvReader::new(Bytes::copy_from_slice(buf));
182        let (typ, value) = r.read_tlv().ok()?;
183        if typ != tlv::FACE_STATUS {
184            return None;
185        }
186        // Advance the caller's cursor past this entry.
187        let consumed = buf.len() - r.remaining();
188        *buf = &buf[consumed..];
189
190        let mut inner = TlvReader::new(value);
191        let mut face_id = 0u64;
192        let mut uri = String::new();
193        let mut local_uri = String::new();
194        let mut face_scope = 0u64;
195        let mut face_persistency = 0u64;
196        let mut link_type = 0u64;
197        let mut mtu = None;
198        let mut base_congestion = None;
199        let mut def_congestion = None;
200        let mut n_in_interests = 0u64;
201        let mut n_in_data = 0u64;
202        let mut n_in_nacks = 0u64;
203        let mut n_out_interests = 0u64;
204        let mut n_out_data = 0u64;
205        let mut n_out_nacks = 0u64;
206        let mut n_in_bytes = 0u64;
207        let mut n_out_bytes = 0u64;
208
209        while !inner.is_empty() {
210            let (t, v) = inner.read_tlv().ok()?;
211            match t {
212                tlv::FACE_ID => face_id = read_non_neg_int(&v)?,
213                tlv::URI => uri = std::str::from_utf8(&v).ok()?.to_owned(),
214                tlv::LOCAL_URI => local_uri = std::str::from_utf8(&v).ok()?.to_owned(),
215                tlv::FACE_SCOPE => face_scope = read_non_neg_int(&v)?,
216                tlv::FACE_PERSISTENCY => face_persistency = read_non_neg_int(&v)?,
217                tlv::LINK_TYPE => link_type = read_non_neg_int(&v)?,
218                tlv::MTU => mtu = read_non_neg_int(&v),
219                tlv::BASE_CONGESTION_MARKING_INTERVAL => {
220                    base_congestion = read_non_neg_int(&v);
221                }
222                tlv::DEFAULT_CONGESTION_THRESHOLD => {
223                    def_congestion = read_non_neg_int(&v);
224                }
225                tlv::N_IN_INTERESTS => n_in_interests = read_non_neg_int(&v)?,
226                tlv::N_IN_DATA => n_in_data = read_non_neg_int(&v)?,
227                tlv::N_IN_NACKS => n_in_nacks = read_non_neg_int(&v)?,
228                tlv::N_OUT_INTERESTS => n_out_interests = read_non_neg_int(&v)?,
229                tlv::N_OUT_DATA => n_out_data = read_non_neg_int(&v)?,
230                tlv::N_OUT_NACKS => n_out_nacks = read_non_neg_int(&v)?,
231                tlv::N_IN_BYTES => n_in_bytes = read_non_neg_int(&v)?,
232                tlv::N_OUT_BYTES => n_out_bytes = read_non_neg_int(&v)?,
233                _ => {} // skip unknown fields
234            }
235        }
236
237        Some(FaceStatus {
238            face_id,
239            uri,
240            local_uri,
241            face_scope,
242            face_persistency,
243            link_type,
244            mtu,
245            base_congestion_marking_interval: base_congestion,
246            default_congestion_threshold: def_congestion,
247            n_in_interests,
248            n_in_data,
249            n_in_nacks,
250            n_out_interests,
251            n_out_data,
252            n_out_nacks,
253            n_in_bytes,
254            n_out_bytes,
255        })
256    }
257
258    /// Decode a concatenated series of FaceStatus entries (full dataset content).
259    pub fn decode_all(bytes: &[u8]) -> Vec<Self> {
260        let mut buf = bytes;
261        let mut out = Vec::new();
262        while !buf.is_empty() {
263            match Self::decode(&mut buf) {
264                Some(entry) => out.push(entry),
265                None => break,
266            }
267        }
268        out
269    }
270
271    /// Persistency label for display.
272    pub fn persistency_str(&self) -> &'static str {
273        match self.face_persistency {
274            0 => "persistent",
275            1 => "on-demand",
276            2 => "permanent",
277            _ => "unknown",
278        }
279    }
280
281    /// Scope label for display.
282    pub fn scope_str(&self) -> &'static str {
283        match self.face_scope {
284            1 => "local",
285            _ => "non-local",
286        }
287    }
288}
289
290// ─── NextHopRecord / FibEntry ─────────────────────────────────────────────────
291
292/// A single nexthop in a FIB entry.
293#[derive(Debug, Clone)]
294pub struct NextHopRecord {
295    pub face_id: u64,
296    pub cost: u64,
297}
298
299/// NFD FibEntry dataset entry (TLV type 0x80).
300///
301/// Returned by `fib/list`.
302#[derive(Debug, Clone)]
303pub struct FibEntry {
304    pub name: Name,
305    pub nexthops: Vec<NextHopRecord>,
306}
307
308impl FibEntry {
309    /// Encode to a complete FibEntry TLV block (type 0x80).
310    pub fn encode(&self) -> Bytes {
311        let mut w = TlvWriter::new();
312        w.write_nested(tlv::ENTRY, |w| {
313            encode_name(w, &self.name);
314            for nh in &self.nexthops {
315                w.write_nested(tlv::NEXT_HOP_RECORD, |w| {
316                    write_non_neg_int(w, tlv::FACE_ID, nh.face_id);
317                    write_non_neg_int(w, tlv::COST, nh.cost);
318                });
319            }
320        });
321        w.finish()
322    }
323
324    /// Decode one FibEntry from the front of `buf`, advancing the cursor.
325    pub fn decode(buf: &mut &[u8]) -> Option<Self> {
326        let mut r = TlvReader::new(Bytes::copy_from_slice(buf));
327        let (typ, value) = r.read_tlv().ok()?;
328        if typ != tlv::ENTRY {
329            return None;
330        }
331        let consumed = buf.len() - r.remaining();
332        *buf = &buf[consumed..];
333
334        let mut inner = TlvReader::new(value);
335        let mut name = None;
336        let mut nexthops = Vec::new();
337
338        while !inner.is_empty() {
339            let (t, v) = inner.read_tlv().ok()?;
340            match t {
341                tlv::NAME => name = decode_name(v),
342                tlv::NEXT_HOP_RECORD => {
343                    let mut nr = TlvReader::new(v);
344                    let mut face_id = 0u64;
345                    let mut cost = 0u64;
346                    while !nr.is_empty() {
347                        if let Ok((nt, nv)) = nr.read_tlv() {
348                            match nt {
349                                tlv::FACE_ID => face_id = read_non_neg_int(&nv).unwrap_or(0),
350                                tlv::COST => cost = read_non_neg_int(&nv).unwrap_or(0),
351                                _ => {}
352                            }
353                        }
354                    }
355                    nexthops.push(NextHopRecord { face_id, cost });
356                }
357                _ => {}
358            }
359        }
360
361        Some(FibEntry {
362            name: name.unwrap_or_else(Name::root),
363            nexthops,
364        })
365    }
366
367    /// Decode a concatenated series of FibEntry entries (full dataset content).
368    pub fn decode_all(bytes: &[u8]) -> Vec<Self> {
369        let mut buf = bytes;
370        let mut out = Vec::new();
371        while !buf.is_empty() {
372            match Self::decode(&mut buf) {
373                Some(entry) => out.push(entry),
374                None => break,
375            }
376        }
377        out
378    }
379}
380
381// ─── Route / RibEntry ─────────────────────────────────────────────────────────
382
383/// A single route in a RIB entry.
384#[derive(Debug, Clone)]
385pub struct Route {
386    pub face_id: u64,
387    pub origin: u64,
388    pub cost: u64,
389    pub flags: u64,
390    pub expiration_period: Option<u64>,
391}
392
393/// NFD RibEntry dataset entry (TLV type 0x80).
394///
395/// Returned by `rib/list`.
396#[derive(Debug, Clone)]
397pub struct RibEntry {
398    pub name: Name,
399    pub routes: Vec<Route>,
400}
401
402impl RibEntry {
403    /// Encode to a complete RibEntry TLV block (type 0x80).
404    pub fn encode(&self) -> Bytes {
405        let mut w = TlvWriter::new();
406        w.write_nested(tlv::ENTRY, |w| {
407            encode_name(w, &self.name);
408            for route in &self.routes {
409                w.write_nested(tlv::ROUTE, |w| {
410                    write_non_neg_int(w, tlv::FACE_ID, route.face_id);
411                    write_non_neg_int(w, tlv::ORIGIN, route.origin);
412                    write_non_neg_int(w, tlv::COST, route.cost);
413                    write_non_neg_int(w, tlv::FLAGS, route.flags);
414                    if let Some(ep) = route.expiration_period {
415                        write_non_neg_int(w, tlv::EXPIRATION_PERIOD, ep);
416                    }
417                });
418            }
419        });
420        w.finish()
421    }
422
423    /// Decode one RibEntry from the front of `buf`, advancing the cursor.
424    pub fn decode(buf: &mut &[u8]) -> Option<Self> {
425        let mut r = TlvReader::new(Bytes::copy_from_slice(buf));
426        let (typ, value) = r.read_tlv().ok()?;
427        if typ != tlv::ENTRY {
428            return None;
429        }
430        let consumed = buf.len() - r.remaining();
431        *buf = &buf[consumed..];
432
433        let mut inner = TlvReader::new(value);
434        let mut name = None;
435        let mut routes = Vec::new();
436
437        while !inner.is_empty() {
438            let (t, v) = inner.read_tlv().ok()?;
439            match t {
440                tlv::NAME => name = decode_name(v),
441                tlv::ROUTE => {
442                    let mut rr = TlvReader::new(v);
443                    let mut face_id = 0u64;
444                    let mut origin = 0u64;
445                    let mut cost = 0u64;
446                    let mut flags = 0u64;
447                    let mut expiration_period = None;
448                    while !rr.is_empty() {
449                        if let Ok((rt, rv)) = rr.read_tlv() {
450                            match rt {
451                                tlv::FACE_ID => {
452                                    face_id = read_non_neg_int(&rv).unwrap_or(0);
453                                }
454                                tlv::ORIGIN => {
455                                    origin = read_non_neg_int(&rv).unwrap_or(0);
456                                }
457                                tlv::COST => {
458                                    cost = read_non_neg_int(&rv).unwrap_or(0);
459                                }
460                                tlv::FLAGS => {
461                                    flags = read_non_neg_int(&rv).unwrap_or(0);
462                                }
463                                tlv::EXPIRATION_PERIOD => {
464                                    expiration_period = read_non_neg_int(&rv);
465                                }
466                                _ => {}
467                            }
468                        }
469                    }
470                    routes.push(Route {
471                        face_id,
472                        origin,
473                        cost,
474                        flags,
475                        expiration_period,
476                    });
477                }
478                _ => {}
479            }
480        }
481
482        Some(RibEntry {
483            name: name.unwrap_or_else(Name::root),
484            routes,
485        })
486    }
487
488    /// Decode a concatenated series of RibEntry entries (full dataset content).
489    pub fn decode_all(bytes: &[u8]) -> Vec<Self> {
490        let mut buf = bytes;
491        let mut out = Vec::new();
492        while !buf.is_empty() {
493            match Self::decode(&mut buf) {
494                Some(entry) => out.push(entry),
495                None => break,
496            }
497        }
498        out
499    }
500}
501
502// ─── StrategyChoice ───────────────────────────────────────────────────────────
503
504/// NFD StrategyChoice dataset entry (TLV type 0x80).
505///
506/// Returned by `strategy-choice/list`.
507#[derive(Debug, Clone)]
508pub struct StrategyChoice {
509    pub name: Name,
510    pub strategy: Name,
511}
512
513impl StrategyChoice {
514    /// Encode to a complete StrategyChoice TLV block (type 0x80).
515    pub fn encode(&self) -> Bytes {
516        let mut w = TlvWriter::new();
517        w.write_nested(tlv::ENTRY, |w| {
518            encode_name(w, &self.name);
519            w.write_nested(tlv::STRATEGY_WRAPPER, |w| {
520                encode_name(w, &self.strategy);
521            });
522        });
523        w.finish()
524    }
525
526    /// Decode one StrategyChoice from the front of `buf`, advancing the cursor.
527    pub fn decode(buf: &mut &[u8]) -> Option<Self> {
528        let mut r = TlvReader::new(Bytes::copy_from_slice(buf));
529        let (typ, value) = r.read_tlv().ok()?;
530        if typ != tlv::ENTRY {
531            return None;
532        }
533        let consumed = buf.len() - r.remaining();
534        *buf = &buf[consumed..];
535
536        let mut inner = TlvReader::new(value);
537        let mut name = None;
538        let mut strategy = None;
539
540        while !inner.is_empty() {
541            let (t, v) = inner.read_tlv().ok()?;
542            match t {
543                tlv::NAME => name = decode_name(v),
544                tlv::STRATEGY_WRAPPER => {
545                    // strategy wrapper contains a Name
546                    let mut sr = TlvReader::new(v);
547                    if let Ok((st, sv)) = sr.read_tlv()
548                        && st == tlv::NAME
549                    {
550                        strategy = decode_name(sv);
551                    }
552                }
553                _ => {}
554            }
555        }
556
557        Some(StrategyChoice {
558            name: name.unwrap_or_else(Name::root),
559            strategy: strategy.unwrap_or_else(Name::root),
560        })
561    }
562
563    /// Decode a concatenated series of StrategyChoice entries (full dataset content).
564    pub fn decode_all(bytes: &[u8]) -> Vec<Self> {
565        let mut buf = bytes;
566        let mut out = Vec::new();
567        while !buf.is_empty() {
568            match Self::decode(&mut buf) {
569                Some(entry) => out.push(entry),
570                None => break,
571            }
572        }
573        out
574    }
575}
576
577// ─── Encode helpers ───────────────────────────────────────────────────────────
578
579/// Concatenate multiple encoded dataset entries into a single `Bytes` buffer.
580pub fn encode_dataset<T, F>(items: &[T], encode_fn: F) -> Bytes
581where
582    F: Fn(&T) -> Bytes,
583{
584    let mut buf = BytesMut::new();
585    for item in items {
586        buf.extend_from_slice(&encode_fn(item));
587    }
588    buf.freeze()
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594    use bytes::Bytes;
595    use ndn_packet::NameComponent;
596
597    fn name(components: &[&[u8]]) -> Name {
598        Name::from_components(
599            components
600                .iter()
601                .map(|c| NameComponent::generic(Bytes::copy_from_slice(c))),
602        )
603    }
604
605    #[test]
606    fn face_status_roundtrip() {
607        let fs = FaceStatus {
608            face_id: 1,
609            uri: "udp4://192.168.1.1:6363".to_owned(),
610            local_uri: "udp4://0.0.0.0:6363".to_owned(),
611            face_scope: 0,
612            face_persistency: 0,
613            link_type: 0,
614            mtu: Some(8800),
615            base_congestion_marking_interval: None,
616            default_congestion_threshold: None,
617            n_in_interests: 100,
618            n_in_data: 50,
619            n_in_nacks: 2,
620            n_out_interests: 80,
621            n_out_data: 30,
622            n_out_nacks: 1,
623            n_in_bytes: 10000,
624            n_out_bytes: 5000,
625        };
626        let encoded = fs.encode();
627        let mut buf = encoded.as_ref();
628        let decoded = FaceStatus::decode(&mut buf).unwrap();
629        assert!(buf.is_empty());
630        assert_eq!(decoded.face_id, 1);
631        assert_eq!(decoded.uri, "udp4://192.168.1.1:6363");
632        assert_eq!(decoded.local_uri, "udp4://0.0.0.0:6363");
633        assert_eq!(decoded.mtu, Some(8800));
634        assert_eq!(decoded.n_in_interests, 100);
635    }
636
637    #[test]
638    fn face_status_decode_all() {
639        let faces = vec![
640            FaceStatus {
641                face_id: 1,
642                uri: "udp4://1.2.3.4:6363".to_owned(),
643                local_uri: "udp4://0.0.0.0:0".to_owned(),
644                face_scope: 0,
645                face_persistency: 0,
646                link_type: 0,
647                mtu: None,
648                base_congestion_marking_interval: None,
649                default_congestion_threshold: None,
650                n_in_interests: 0,
651                n_in_data: 0,
652                n_in_nacks: 0,
653                n_out_interests: 0,
654                n_out_data: 0,
655                n_out_nacks: 0,
656                n_in_bytes: 0,
657                n_out_bytes: 0,
658            },
659            FaceStatus {
660                face_id: 2,
661                uri: "tcp4://5.6.7.8:6363".to_owned(),
662                local_uri: "tcp4://0.0.0.0:0".to_owned(),
663                face_scope: 1,
664                face_persistency: 2,
665                link_type: 0,
666                mtu: None,
667                base_congestion_marking_interval: None,
668                default_congestion_threshold: None,
669                n_in_interests: 0,
670                n_in_data: 0,
671                n_in_nacks: 0,
672                n_out_interests: 0,
673                n_out_data: 0,
674                n_out_nacks: 0,
675                n_in_bytes: 0,
676                n_out_bytes: 0,
677            },
678        ];
679        let mut buf = BytesMut::new();
680        for f in &faces {
681            buf.extend_from_slice(&f.encode());
682        }
683        let decoded = FaceStatus::decode_all(&buf);
684        assert_eq!(decoded.len(), 2);
685        assert_eq!(decoded[0].face_id, 1);
686        assert_eq!(decoded[1].face_id, 2);
687        assert_eq!(decoded[1].face_persistency, 2);
688    }
689
690    #[test]
691    fn fib_entry_roundtrip() {
692        let entry = FibEntry {
693            name: name(&[b"ndn", b"test"]),
694            nexthops: vec![
695                NextHopRecord {
696                    face_id: 1,
697                    cost: 10,
698                },
699                NextHopRecord {
700                    face_id: 2,
701                    cost: 5,
702                },
703            ],
704        };
705        let encoded = entry.encode();
706        let mut buf = encoded.as_ref();
707        let decoded = FibEntry::decode(&mut buf).unwrap();
708        assert!(buf.is_empty());
709        assert_eq!(decoded.nexthops.len(), 2);
710        assert_eq!(decoded.nexthops[0].face_id, 1);
711        assert_eq!(decoded.nexthops[0].cost, 10);
712        assert_eq!(decoded.nexthops[1].face_id, 2);
713        assert_eq!(decoded.nexthops[1].cost, 5);
714    }
715
716    #[test]
717    fn rib_entry_roundtrip() {
718        let entry = RibEntry {
719            name: name(&[b"ndn"]),
720            routes: vec![Route {
721                face_id: 3,
722                origin: 0,
723                cost: 10,
724                flags: 1,
725                expiration_period: Some(30_000),
726            }],
727        };
728        let encoded = entry.encode();
729        let mut buf = encoded.as_ref();
730        let decoded = RibEntry::decode(&mut buf).unwrap();
731        assert!(buf.is_empty());
732        assert_eq!(decoded.routes.len(), 1);
733        assert_eq!(decoded.routes[0].face_id, 3);
734        assert_eq!(decoded.routes[0].expiration_period, Some(30_000));
735    }
736
737    #[test]
738    fn strategy_choice_roundtrip() {
739        let entry = StrategyChoice {
740            name: name(&[b"ndn"]),
741            strategy: name(&[b"localhost", b"nfd", b"strategy", b"best-route"]),
742        };
743        let encoded = entry.encode();
744        let mut buf = encoded.as_ref();
745        let decoded = StrategyChoice::decode(&mut buf).unwrap();
746        assert!(buf.is_empty());
747        assert_eq!(
748            decoded.strategy.to_string(),
749            "/localhost/nfd/strategy/best-route"
750        );
751    }
752}