Skip to main content

rustbgpd_wire/
nlri.rs

1use std::fmt;
2use std::net::{Ipv4Addr, Ipv6Addr};
3
4use crate::error::DecodeError;
5
6/// An IPv4 NLRI entry with an optional Add-Path path ID (RFC 7911).
7///
8/// For non-Add-Path peers, `path_id` is always 0.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Ipv4NlriEntry {
11    /// Add-Path path identifier (0 when Add-Path is not in use).
12    pub path_id: u32,
13    /// The IPv4 prefix.
14    pub prefix: Ipv4Prefix,
15}
16
17/// A generic NLRI entry (IPv4 or IPv6) with an optional Add-Path path ID.
18///
19/// For non-Add-Path peers, `path_id` is always 0.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct NlriEntry {
22    /// Add-Path path identifier (0 when Add-Path is not in use).
23    pub path_id: u32,
24    /// The prefix (IPv4 or IPv6).
25    pub prefix: Prefix,
26}
27
28/// An IPv4 prefix (network address + prefix length).
29///
30/// Stored in canonical form: host bits are always zero.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct Ipv4Prefix {
33    /// Network address (host bits zeroed).
34    pub addr: Ipv4Addr,
35    /// Prefix length in bits (0–32).
36    pub len: u8,
37}
38
39impl Ipv4Prefix {
40    /// Create a new prefix, masking off host bits.
41    ///
42    /// Prefix length is clamped to 32 (values above 32 are silently
43    /// reduced). This is intentional: wire decoders validate prefix
44    /// lengths before construction, and clamping is safer than panicking
45    /// for internal callers that may compute lengths arithmetically.
46    #[must_use]
47    pub fn new(addr: Ipv4Addr, len: u8) -> Self {
48        let len = len.min(32);
49        let masked = if len == 0 {
50            0
51        } else if len >= 32 {
52            u32::from(addr)
53        } else {
54            u32::from(addr) & !((1u32 << (32 - len)) - 1)
55        };
56        Self {
57            addr: Ipv4Addr::from(masked),
58            len,
59        }
60    }
61}
62
63impl fmt::Display for Ipv4Prefix {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "{}/{}", self.addr, self.len)
66    }
67}
68
69/// Decode a sequence of NLRI prefixes from wire format (RFC 4271 §4.3).
70///
71/// Each prefix is encoded as a length byte (0–32) followed by the minimum
72/// number of address bytes (`ceil(len / 8)`).
73///
74/// # Errors
75///
76/// Returns `DecodeError` if a prefix length exceeds 32 or the buffer is
77/// truncated mid-prefix.
78pub fn decode_nlri(mut buf: &[u8]) -> Result<Vec<Ipv4Prefix>, DecodeError> {
79    let mut prefixes = Vec::new();
80
81    while !buf.is_empty() {
82        let field_start = buf;
83        let prefix_len = buf[0];
84        buf = &buf[1..];
85
86        if prefix_len > 32 {
87            // Include the length byte + available address bytes in error data
88            let addr_bytes = usize::from(prefix_len.div_ceil(8)).min(buf.len());
89            return Err(DecodeError::InvalidNetworkField {
90                detail: format!("NLRI prefix length {prefix_len} exceeds 32"),
91                data: field_start[..=addr_bytes].to_vec(),
92            });
93        }
94
95        let byte_count = usize::from(prefix_len.div_ceil(8));
96        if buf.len() < byte_count {
97            // Truncated NLRI is also an Invalid Network Field, not a header
98            // framing error. Include the length byte + available bytes.
99            return Err(DecodeError::InvalidNetworkField {
100                detail: format!(
101                    "NLRI truncated: prefix length {prefix_len} requires \
102                     {byte_count} bytes, have {}",
103                    buf.len()
104                ),
105                data: field_start[..=buf.len()].to_vec(),
106            });
107        }
108
109        let mut octets = [0u8; 4];
110        octets[..byte_count].copy_from_slice(&buf[..byte_count]);
111        buf = &buf[byte_count..];
112
113        prefixes.push(Ipv4Prefix::new(Ipv4Addr::from(octets), prefix_len));
114    }
115
116    Ok(prefixes)
117}
118
119/// Encode a sequence of NLRI prefixes into wire format.
120pub fn encode_nlri(prefixes: &[Ipv4Prefix], buf: &mut Vec<u8>) {
121    for prefix in prefixes {
122        buf.push(prefix.len);
123        let byte_count = usize::from(prefix.len.div_ceil(8));
124        let octets = prefix.addr.octets();
125        buf.extend_from_slice(&octets[..byte_count]);
126    }
127}
128
129/// An IPv6 prefix (network address + prefix length).
130///
131/// Stored in canonical form: host bits are always zero.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
133pub struct Ipv6Prefix {
134    /// Network address (host bits zeroed).
135    pub addr: Ipv6Addr,
136    /// Prefix length in bits (0–128).
137    pub len: u8,
138}
139
140impl Ipv6Prefix {
141    /// Create a new prefix, masking off host bits.
142    ///
143    /// Prefix length is clamped to 128 (values above 128 are silently
144    /// reduced). This is intentional: wire decoders validate prefix
145    /// lengths before construction, and clamping is safer than panicking
146    /// for internal callers that may compute lengths arithmetically.
147    #[must_use]
148    pub fn new(addr: Ipv6Addr, len: u8) -> Self {
149        let len = len.min(128);
150        let masked = if len == 0 {
151            0u128
152        } else if len >= 128 {
153            u128::from(addr)
154        } else {
155            u128::from(addr) & !((1u128 << (128 - len)) - 1)
156        };
157        Self {
158            addr: Ipv6Addr::from(masked),
159            len,
160        }
161    }
162}
163
164impl fmt::Display for Ipv6Prefix {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        write!(f, "{}/{}", self.addr, self.len)
167    }
168}
169
170/// A prefix that can be either IPv4 or IPv6.
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172pub enum Prefix {
173    /// IPv4 prefix.
174    V4(Ipv4Prefix),
175    /// IPv6 prefix.
176    V6(Ipv6Prefix),
177}
178
179impl Prefix {
180    /// Return the network address as a string.
181    #[must_use]
182    pub fn addr_string(&self) -> String {
183        match self {
184            Self::V4(p) => p.addr.to_string(),
185            Self::V6(p) => p.addr.to_string(),
186        }
187    }
188
189    /// Return the prefix length.
190    #[must_use]
191    pub fn prefix_len(&self) -> u8 {
192        match self {
193            Self::V4(p) => p.len,
194            Self::V6(p) => p.len,
195        }
196    }
197}
198
199impl fmt::Display for Prefix {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        match self {
202            Self::V4(p) => p.fmt(f),
203            Self::V6(p) => p.fmt(f),
204        }
205    }
206}
207
208/// Decode a sequence of IPv6 NLRI prefixes from wire format.
209///
210/// Same encoding as IPv4 NLRI: length byte (0–128) followed by the minimum
211/// number of address bytes (`ceil(len / 8)`) up to 16.
212///
213/// # Errors
214///
215/// Returns `DecodeError` if a prefix length exceeds 128 or the buffer is
216/// truncated mid-prefix.
217pub fn decode_ipv6_nlri(mut buf: &[u8]) -> Result<Vec<Ipv6Prefix>, DecodeError> {
218    let mut prefixes = Vec::new();
219
220    while !buf.is_empty() {
221        let field_start = buf;
222        let prefix_len = buf[0];
223        buf = &buf[1..];
224
225        if prefix_len > 128 {
226            let addr_bytes = usize::from(prefix_len.div_ceil(8)).min(buf.len());
227            return Err(DecodeError::InvalidNetworkField {
228                detail: format!("NLRI prefix length {prefix_len} exceeds 128"),
229                data: field_start[..=addr_bytes].to_vec(),
230            });
231        }
232
233        let byte_count = usize::from(prefix_len.div_ceil(8));
234        if buf.len() < byte_count {
235            return Err(DecodeError::InvalidNetworkField {
236                detail: format!(
237                    "NLRI truncated: prefix length {prefix_len} requires \
238                     {byte_count} bytes, have {}",
239                    buf.len()
240                ),
241                data: field_start[..=buf.len()].to_vec(),
242            });
243        }
244
245        let mut octets = [0u8; 16];
246        octets[..byte_count].copy_from_slice(&buf[..byte_count]);
247        buf = &buf[byte_count..];
248
249        prefixes.push(Ipv6Prefix::new(Ipv6Addr::from(octets), prefix_len));
250    }
251
252    Ok(prefixes)
253}
254
255/// Encode a sequence of IPv6 NLRI prefixes into wire format.
256pub fn encode_ipv6_nlri(prefixes: &[Ipv6Prefix], buf: &mut Vec<u8>) {
257    for prefix in prefixes {
258        buf.push(prefix.len);
259        let byte_count = usize::from(prefix.len.div_ceil(8));
260        let octets = prefix.addr.octets();
261        buf.extend_from_slice(&octets[..byte_count]);
262    }
263}
264
265/// Decode a sequence of Add-Path IPv4 NLRI entries (RFC 7911 §3).
266///
267/// Wire format: `[4-byte path_id BE][prefix_len][prefix_bytes...]` per entry.
268///
269/// # Errors
270///
271/// Returns `DecodeError` if the buffer is truncated or a prefix length exceeds 32.
272pub fn decode_nlri_addpath(mut buf: &[u8]) -> Result<Vec<Ipv4NlriEntry>, DecodeError> {
273    let mut entries = Vec::new();
274
275    while !buf.is_empty() {
276        if buf.len() < 5 {
277            return Err(DecodeError::InvalidNetworkField {
278                detail: format!(
279                    "Add-Path NLRI truncated: need at least 5 bytes (path_id + prefix_len), have {}",
280                    buf.len()
281                ),
282                data: buf.to_vec(),
283            });
284        }
285
286        let path_id = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
287        buf = &buf[4..];
288
289        let field_start = buf;
290        let prefix_len = buf[0];
291        buf = &buf[1..];
292
293        if prefix_len > 32 {
294            let addr_bytes = usize::from(prefix_len.div_ceil(8)).min(buf.len());
295            return Err(DecodeError::InvalidNetworkField {
296                detail: format!("NLRI prefix length {prefix_len} exceeds 32"),
297                data: field_start[..=addr_bytes].to_vec(),
298            });
299        }
300
301        let byte_count = usize::from(prefix_len.div_ceil(8));
302        if buf.len() < byte_count {
303            return Err(DecodeError::InvalidNetworkField {
304                detail: format!(
305                    "NLRI truncated: prefix length {prefix_len} requires \
306                     {byte_count} bytes, have {}",
307                    buf.len()
308                ),
309                data: field_start[..=buf.len()].to_vec(),
310            });
311        }
312
313        let mut octets = [0u8; 4];
314        octets[..byte_count].copy_from_slice(&buf[..byte_count]);
315        buf = &buf[byte_count..];
316
317        entries.push(Ipv4NlriEntry {
318            path_id,
319            prefix: Ipv4Prefix::new(Ipv4Addr::from(octets), prefix_len),
320        });
321    }
322
323    Ok(entries)
324}
325
326/// Encode a sequence of Add-Path IPv4 NLRI entries into wire format.
327pub fn encode_nlri_addpath(entries: &[Ipv4NlriEntry], buf: &mut Vec<u8>) {
328    for entry in entries {
329        buf.extend_from_slice(&entry.path_id.to_be_bytes());
330        buf.push(entry.prefix.len);
331        let byte_count = usize::from(entry.prefix.len.div_ceil(8));
332        let octets = entry.prefix.addr.octets();
333        buf.extend_from_slice(&octets[..byte_count]);
334    }
335}
336
337/// Decode a sequence of Add-Path IPv6 NLRI entries (RFC 7911 §3).
338///
339/// Wire format: `[4-byte path_id BE][prefix_len][prefix_bytes...]` per entry.
340///
341/// # Errors
342///
343/// Returns `DecodeError` if the buffer is truncated or a prefix length exceeds 128.
344pub fn decode_ipv6_nlri_addpath(mut buf: &[u8]) -> Result<Vec<NlriEntry>, DecodeError> {
345    let mut entries = Vec::new();
346
347    while !buf.is_empty() {
348        if buf.len() < 5 {
349            return Err(DecodeError::InvalidNetworkField {
350                detail: format!(
351                    "Add-Path NLRI truncated: need at least 5 bytes (path_id + prefix_len), have {}",
352                    buf.len()
353                ),
354                data: buf.to_vec(),
355            });
356        }
357
358        let path_id = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
359        buf = &buf[4..];
360
361        let field_start = buf;
362        let prefix_len = buf[0];
363        buf = &buf[1..];
364
365        if prefix_len > 128 {
366            let addr_bytes = usize::from(prefix_len.div_ceil(8)).min(buf.len());
367            return Err(DecodeError::InvalidNetworkField {
368                detail: format!("NLRI prefix length {prefix_len} exceeds 128"),
369                data: field_start[..=addr_bytes].to_vec(),
370            });
371        }
372
373        let byte_count = usize::from(prefix_len.div_ceil(8));
374        if buf.len() < byte_count {
375            return Err(DecodeError::InvalidNetworkField {
376                detail: format!(
377                    "NLRI truncated: prefix length {prefix_len} requires \
378                     {byte_count} bytes, have {}",
379                    buf.len()
380                ),
381                data: field_start[..=buf.len()].to_vec(),
382            });
383        }
384
385        let mut octets = [0u8; 16];
386        octets[..byte_count].copy_from_slice(&buf[..byte_count]);
387        buf = &buf[byte_count..];
388
389        entries.push(NlriEntry {
390            path_id,
391            prefix: Prefix::V6(Ipv6Prefix::new(Ipv6Addr::from(octets), prefix_len)),
392        });
393    }
394
395    Ok(entries)
396}
397
398/// Encode a sequence of Add-Path IPv6 NLRI entries into wire format.
399pub fn encode_ipv6_nlri_addpath(entries: &[NlriEntry], buf: &mut Vec<u8>) {
400    for entry in entries {
401        buf.extend_from_slice(&entry.path_id.to_be_bytes());
402        match entry.prefix {
403            Prefix::V6(p) => {
404                buf.push(p.len);
405                let byte_count = usize::from(p.len.div_ceil(8));
406                let octets = p.addr.octets();
407                buf.extend_from_slice(&octets[..byte_count]);
408            }
409            Prefix::V4(p) => {
410                buf.push(p.len);
411                let byte_count = usize::from(p.len.div_ceil(8));
412                let octets = p.addr.octets();
413                buf.extend_from_slice(&octets[..byte_count]);
414            }
415        }
416    }
417}
418
419/// Decode Add-Path IPv4 NLRI entries into generic `NlriEntry` (for `MP_REACH`/`MP_UNREACH`).
420///
421/// # Errors
422///
423/// Returns `DecodeError` if the buffer is truncated or a prefix length exceeds 32.
424pub fn decode_nlri_addpath_generic(buf: &[u8]) -> Result<Vec<NlriEntry>, DecodeError> {
425    decode_nlri_addpath(buf).map(|entries| {
426        entries
427            .into_iter()
428            .map(|e| NlriEntry {
429                path_id: e.path_id,
430                prefix: Prefix::V4(e.prefix),
431            })
432            .collect()
433    })
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439
440    #[test]
441    fn roundtrip_single_prefix() {
442        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24);
443        let mut buf = Vec::new();
444        encode_nlri(&[prefix], &mut buf);
445        let decoded = decode_nlri(&buf).unwrap();
446        assert_eq!(decoded, vec![prefix]);
447    }
448
449    #[test]
450    fn roundtrip_multiple_prefixes() {
451        let prefixes = vec![
452            Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8),
453            Ipv4Prefix::new(Ipv4Addr::new(192, 168, 1, 0), 24),
454            Ipv4Prefix::new(Ipv4Addr::new(172, 16, 0, 0), 12),
455        ];
456        let mut buf = Vec::new();
457        encode_nlri(&prefixes, &mut buf);
458        let decoded = decode_nlri(&buf).unwrap();
459        assert_eq!(decoded, prefixes);
460    }
461
462    #[test]
463    fn prefix_len_zero() {
464        let prefix = Ipv4Prefix::new(Ipv4Addr::UNSPECIFIED, 0);
465        assert_eq!(prefix.addr, Ipv4Addr::UNSPECIFIED);
466        let mut buf = Vec::new();
467        encode_nlri(&[prefix], &mut buf);
468        assert_eq!(buf, vec![0]); // just the length byte, no address bytes
469        let decoded = decode_nlri(&buf).unwrap();
470        assert_eq!(decoded, vec![prefix]);
471    }
472
473    #[test]
474    fn prefix_len_32() {
475        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 1, 2, 3), 32);
476        let mut buf = Vec::new();
477        encode_nlri(&[prefix], &mut buf);
478        assert_eq!(buf, vec![32, 10, 1, 2, 3]);
479        let decoded = decode_nlri(&buf).unwrap();
480        assert_eq!(decoded, vec![prefix]);
481    }
482
483    #[test]
484    fn host_bits_masked() {
485        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 255), 24);
486        assert_eq!(prefix.addr, Ipv4Addr::new(10, 0, 0, 0));
487    }
488
489    #[test]
490    fn reject_prefix_len_exceeds_32() {
491        let buf = [33, 10, 0, 0, 0];
492        let err = decode_nlri(&buf).unwrap_err();
493        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
494        let (code, subcode, data) = err.to_notification();
495        assert_eq!(code, crate::notification::NotificationCode::UpdateMessage);
496        assert_eq!(subcode, 10);
497        // Error data includes the length byte + available address bytes
498        assert_eq!(data.as_ref(), &[33, 10, 0, 0, 0]);
499    }
500
501    #[test]
502    fn reject_truncated_buffer() {
503        // /24 needs 3 bytes but only 2 provided
504        let buf = [24, 10, 0];
505        let err = decode_nlri(&buf).unwrap_err();
506        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
507        let (code, subcode, data) = err.to_notification();
508        assert_eq!(code, crate::notification::NotificationCode::UpdateMessage);
509        assert_eq!(subcode, 10);
510        // Error data includes the length byte + available bytes
511        assert_eq!(data.as_ref(), &[24, 10, 0]);
512    }
513
514    #[test]
515    fn empty_buffer_yields_empty_vec() {
516        let decoded = decode_nlri(&[]).unwrap();
517        assert!(decoded.is_empty());
518    }
519
520    #[test]
521    fn display_format() {
522        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24);
523        assert_eq!(format!("{prefix}"), "10.0.0.0/24");
524    }
525
526    #[test]
527    fn wire_encoding_10_0_slash_24() {
528        // 10.0.0.0/24 → [24, 10, 0, 0] (3 address bytes)
529        let buf = [24, 10, 0, 0];
530        let decoded = decode_nlri(&buf).unwrap();
531        assert_eq!(decoded.len(), 1);
532        assert_eq!(decoded[0].addr, Ipv4Addr::new(10, 0, 0, 0));
533        assert_eq!(decoded[0].len, 24);
534    }
535
536    #[test]
537    fn wire_encoding_odd_prefix_len() {
538        // 10.128.0.0/9 → [9, 10, 128] (2 address bytes)
539        let buf = [9, 10, 128];
540        let decoded = decode_nlri(&buf).unwrap();
541        assert_eq!(decoded.len(), 1);
542        assert_eq!(decoded[0].addr, Ipv4Addr::new(10, 128, 0, 0));
543        assert_eq!(decoded[0].len, 9);
544    }
545
546    // --- IPv6 prefix tests ---
547
548    #[test]
549    fn ipv6_prefix_new_masks_host_bits() {
550        let prefix = Ipv6Prefix::new("2001:db8::ffff".parse().unwrap(), 32);
551        assert_eq!(prefix.addr, "2001:db8::".parse::<Ipv6Addr>().unwrap());
552        assert_eq!(prefix.len, 32);
553    }
554
555    #[test]
556    fn ipv6_prefix_len_zero() {
557        let prefix = Ipv6Prefix::new(Ipv6Addr::UNSPECIFIED, 0);
558        assert_eq!(prefix.addr, Ipv6Addr::UNSPECIFIED);
559    }
560
561    #[test]
562    fn ipv6_prefix_len_128() {
563        let addr: Ipv6Addr = "2001:db8::1".parse().unwrap();
564        let prefix = Ipv6Prefix::new(addr, 128);
565        assert_eq!(prefix.addr, addr);
566        assert_eq!(prefix.len, 128);
567    }
568
569    #[test]
570    fn ipv6_prefix_display() {
571        let prefix = Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32);
572        assert_eq!(format!("{prefix}"), "2001:db8::/32");
573    }
574
575    #[test]
576    fn ipv6_nlri_roundtrip_single() {
577        let prefix = Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32);
578        let mut buf = Vec::new();
579        encode_ipv6_nlri(&[prefix], &mut buf);
580        let decoded = decode_ipv6_nlri(&buf).unwrap();
581        assert_eq!(decoded, vec![prefix]);
582    }
583
584    #[test]
585    fn ipv6_nlri_roundtrip_multiple() {
586        let prefixes = vec![
587            Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32),
588            Ipv6Prefix::new("fd00::".parse().unwrap(), 64),
589            Ipv6Prefix::new("::1".parse().unwrap(), 128),
590        ];
591        let mut buf = Vec::new();
592        encode_ipv6_nlri(&prefixes, &mut buf);
593        let decoded = decode_ipv6_nlri(&buf).unwrap();
594        assert_eq!(decoded, prefixes);
595    }
596
597    #[test]
598    fn ipv6_nlri_len_zero() {
599        let prefix = Ipv6Prefix::new(Ipv6Addr::UNSPECIFIED, 0);
600        let mut buf = Vec::new();
601        encode_ipv6_nlri(&[prefix], &mut buf);
602        assert_eq!(buf, vec![0]); // just the length byte
603        let decoded = decode_ipv6_nlri(&buf).unwrap();
604        assert_eq!(decoded, vec![prefix]);
605    }
606
607    #[test]
608    fn ipv6_nlri_reject_exceeds_128() {
609        let buf = [129, 0x20, 0x01];
610        let err = decode_ipv6_nlri(&buf).unwrap_err();
611        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
612    }
613
614    #[test]
615    fn ipv6_nlri_reject_truncated() {
616        // /64 needs 8 bytes but only 4 provided
617        let buf = [64, 0x20, 0x01, 0x0d, 0xb8];
618        let err = decode_ipv6_nlri(&buf).unwrap_err();
619        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
620    }
621
622    #[test]
623    fn ipv6_nlri_empty_buffer() {
624        let decoded = decode_ipv6_nlri(&[]).unwrap();
625        assert!(decoded.is_empty());
626    }
627
628    // --- Prefix enum tests ---
629
630    #[test]
631    fn prefix_display_v4() {
632        let p = Prefix::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24));
633        assert_eq!(format!("{p}"), "10.0.0.0/24");
634    }
635
636    #[test]
637    fn ipv4_prefix_clamps_length() {
638        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 33);
639        assert_eq!(prefix.len, 32);
640        let prefix = Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 255);
641        assert_eq!(prefix.len, 32);
642    }
643
644    #[test]
645    fn ipv6_prefix_clamps_length() {
646        let prefix = Ipv6Prefix::new("2001:db8::".parse().unwrap(), 129);
647        assert_eq!(prefix.len, 128);
648        let prefix = Ipv6Prefix::new("2001:db8::".parse().unwrap(), 200);
649        assert_eq!(prefix.len, 128);
650    }
651
652    #[test]
653    fn prefix_display_v6() {
654        let p = Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32));
655        assert_eq!(format!("{p}"), "2001:db8::/32");
656    }
657
658    // --- Add-Path IPv4 NLRI tests ---
659
660    #[test]
661    fn addpath_ipv4_roundtrip_single() {
662        let entry = Ipv4NlriEntry {
663            path_id: 42,
664            prefix: Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24),
665        };
666        let mut buf = Vec::new();
667        encode_nlri_addpath(&[entry], &mut buf);
668        let decoded = decode_nlri_addpath(&buf).unwrap();
669        assert_eq!(decoded, vec![entry]);
670    }
671
672    #[test]
673    fn addpath_ipv4_roundtrip_multiple() {
674        let entries = vec![
675            Ipv4NlriEntry {
676                path_id: 1,
677                prefix: Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8),
678            },
679            Ipv4NlriEntry {
680                path_id: 2,
681                prefix: Ipv4Prefix::new(Ipv4Addr::new(192, 168, 1, 0), 24),
682            },
683            Ipv4NlriEntry {
684                path_id: 0xFFFF_FFFF,
685                prefix: Ipv4Prefix::new(Ipv4Addr::new(172, 16, 0, 0), 12),
686            },
687        ];
688        let mut buf = Vec::new();
689        encode_nlri_addpath(&entries, &mut buf);
690        let decoded = decode_nlri_addpath(&buf).unwrap();
691        assert_eq!(decoded, entries);
692    }
693
694    #[test]
695    fn addpath_ipv4_empty() {
696        let decoded = decode_nlri_addpath(&[]).unwrap();
697        assert!(decoded.is_empty());
698    }
699
700    #[test]
701    fn addpath_ipv4_truncated_path_id() {
702        // Only 3 bytes — not enough for a 4-byte path ID + prefix len
703        let buf = [0, 0, 0];
704        assert!(decode_nlri_addpath(&buf).is_err());
705    }
706
707    #[test]
708    fn addpath_ipv4_prefix_len_exceeds_32() {
709        // path_id=1, prefix_len=33
710        let buf = [0, 0, 0, 1, 33, 10, 0, 0, 0, 0];
711        assert!(decode_nlri_addpath(&buf).is_err());
712    }
713
714    #[test]
715    fn addpath_ipv4_truncated_prefix() {
716        // path_id=1, prefix_len=24, but only 2 address bytes (need 3)
717        let buf = [0, 0, 0, 1, 24, 10, 0];
718        assert!(decode_nlri_addpath(&buf).is_err());
719    }
720
721    #[test]
722    fn addpath_ipv4_wire_format() {
723        let entry = Ipv4NlriEntry {
724            path_id: 1,
725            prefix: Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24),
726        };
727        let mut buf = Vec::new();
728        encode_nlri_addpath(&[entry], &mut buf);
729        // 4-byte path_id (BE) + 1-byte len + 3-byte addr
730        assert_eq!(buf, vec![0, 0, 0, 1, 24, 10, 0, 0]);
731    }
732
733    // --- Add-Path IPv6 NLRI tests ---
734
735    #[test]
736    fn addpath_ipv6_roundtrip_single() {
737        let entry = NlriEntry {
738            path_id: 7,
739            prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
740        };
741        let mut buf = Vec::new();
742        encode_ipv6_nlri_addpath(&[entry], &mut buf);
743        let decoded = decode_ipv6_nlri_addpath(&buf).unwrap();
744        assert_eq!(decoded, vec![entry]);
745    }
746
747    #[test]
748    fn addpath_ipv6_roundtrip_multiple() {
749        let entries = vec![
750            NlriEntry {
751                path_id: 1,
752                prefix: Prefix::V6(Ipv6Prefix::new("2001:db8::".parse().unwrap(), 32)),
753            },
754            NlriEntry {
755                path_id: 2,
756                prefix: Prefix::V6(Ipv6Prefix::new("fd00::".parse().unwrap(), 64)),
757            },
758        ];
759        let mut buf = Vec::new();
760        encode_ipv6_nlri_addpath(&entries, &mut buf);
761        let decoded = decode_ipv6_nlri_addpath(&buf).unwrap();
762        assert_eq!(decoded, entries);
763    }
764
765    #[test]
766    fn addpath_ipv6_empty() {
767        let decoded = decode_ipv6_nlri_addpath(&[]).unwrap();
768        assert!(decoded.is_empty());
769    }
770
771    #[test]
772    fn addpath_ipv6_truncated() {
773        let buf = [0, 0, 0];
774        assert!(decode_ipv6_nlri_addpath(&buf).is_err());
775    }
776
777    #[test]
778    fn addpath_generic_ipv4_roundtrip() {
779        let entries = vec![Ipv4NlriEntry {
780            path_id: 1,
781            prefix: Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24),
782        }];
783        let mut buf = Vec::new();
784        encode_nlri_addpath(&entries, &mut buf);
785        let generic = decode_nlri_addpath_generic(&buf).unwrap();
786        assert_eq!(generic.len(), 1);
787        assert_eq!(generic[0].path_id, 1);
788        assert_eq!(
789            generic[0].prefix,
790            Prefix::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 24))
791        );
792    }
793}