Skip to main content

rustbgpd_wire/
vpn.rs

1//! VPNv4/VPNv6 labeled NLRI codec substrate.
2//!
3//! This module is deliberately pure and unreachable from MP_REACH/MP_UNREACH
4//! dispatch today. It provides the RFC 8277 label-stack and RFC 4364 / RFC
5//! 4659 RD-prefixed VPN prefix codec pieces needed for a future typed
6//! VPNv4/VPNv6 route-reflector slice without treating VPN routes as unicast
7//! [`crate::nlri::Prefix`] values.
8
9use std::fmt;
10use std::net::{Ipv4Addr, Ipv6Addr};
11
12use crate::error::{DecodeError, EncodeError};
13use crate::evpn::RouteDistinguisher;
14
15/// AFI for `VPNv4` labeled NLRI (RFC 4364 §4.3.1).
16pub const VPNV4_AFI: u16 = 1;
17/// AFI for `VPNv6` labeled NLRI (RFC 4659 §3.2).
18pub const VPNV6_AFI: u16 = 2;
19/// SAFI for labeled-unicast NLRI (RFC 8277 §2).
20pub const LABELED_UNICAST_SAFI: u8 = 4;
21/// SAFI for labeled VPN NLRI (RFC 8277 §2, RFC 4364, RFC 4659).
22pub const MPLS_VPN_SAFI: u8 = 128;
23/// Route Distinguisher length in octets.
24pub const ROUTE_DISTINGUISHER_LEN: usize = 8;
25/// Route Distinguisher length in bits.
26pub const ROUTE_DISTINGUISHER_BITS: u8 = 64;
27/// One MPLS label-stack entry is a 20-bit label, 3-bit TC, and S bit.
28pub const MPLS_LABEL_ENTRY_BITS: u8 = 24;
29/// One MPLS label-stack entry length in octets.
30pub const MPLS_LABEL_ENTRY_LEN: usize = 3;
31/// Maximum 20-bit MPLS label value.
32pub const MAX_MPLS_LABEL: u32 = 0x000F_FFFF;
33/// Maximum 3-bit MPLS traffic-class value.
34pub const MAX_MPLS_TRAFFIC_CLASS: u8 = 0x07;
35
36/// VPN address family carried by a VPNv4/VPNv6 NLRI.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub enum VpnAddressFamily {
39    /// VPN-IPv4, AFI 1 / SAFI 128.
40    V4,
41    /// VPN-IPv6, AFI 2 / SAFI 128.
42    V6,
43}
44
45impl VpnAddressFamily {
46    /// Return the AFI value for this VPN family.
47    #[must_use]
48    pub const fn afi(self) -> u16 {
49        match self {
50            Self::V4 => VPNV4_AFI,
51            Self::V6 => VPNV6_AFI,
52        }
53    }
54
55    const fn max_prefix_len(self) -> u8 {
56        match self {
57            Self::V4 => 32,
58            Self::V6 => 128,
59        }
60    }
61}
62
63impl fmt::Display for VpnAddressFamily {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::V4 => write!(f, "vpnv4"),
67            Self::V6 => write!(f, "vpnv6"),
68        }
69    }
70}
71
72/// One RFC 8277 MPLS label-stack entry.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
74pub struct MplsLabelEntry {
75    /// The 20-bit MPLS label value.
76    pub label: u32,
77    /// The 3-bit traffic-class field.
78    pub traffic_class: u8,
79    /// Bottom-of-stack bit.
80    pub bottom_of_stack: bool,
81}
82
83impl MplsLabelEntry {
84    /// Construct a validated MPLS label-stack entry.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`EncodeError::ValueOutOfRange`] when `label` exceeds 20 bits
89    /// or `traffic_class` exceeds 3 bits.
90    pub fn try_new(
91        label: u32,
92        traffic_class: u8,
93        bottom_of_stack: bool,
94    ) -> Result<Self, EncodeError> {
95        validate_label(label)?;
96        validate_traffic_class(traffic_class)?;
97        Ok(Self {
98            label,
99            traffic_class,
100            bottom_of_stack,
101        })
102    }
103
104    /// Decode a raw 24-bit label-stack entry.
105    #[expect(
106        clippy::cast_possible_truncation,
107        reason = "traffic-class value is masked to 3 bits before the cast"
108    )]
109    #[must_use]
110    pub const fn from_raw(raw: u32) -> Self {
111        Self {
112            label: (raw >> 4) & MAX_MPLS_LABEL,
113            traffic_class: ((raw >> 1) & MAX_MPLS_TRAFFIC_CLASS as u32) as u8,
114            bottom_of_stack: (raw & 0x01) != 0,
115        }
116    }
117
118    /// Encode this entry as a raw 24-bit wire value.
119    ///
120    /// # Errors
121    ///
122    /// Returns [`EncodeError::ValueOutOfRange`] if the public fields contain
123    /// out-of-range values.
124    pub fn raw_value(&self) -> Result<u32, EncodeError> {
125        validate_label(self.label)?;
126        validate_traffic_class(self.traffic_class)?;
127        Ok((self.label << 4)
128            | (u32::from(self.traffic_class) << 1)
129            | u32::from(self.bottom_of_stack))
130    }
131}
132
133/// A `VPNv4` or `VPNv6` route prefix keyed by RD plus address prefix.
134///
135/// This is intentionally not [`crate::nlri::Prefix`]. VPN route identity is
136/// different from ordinary IPv4/IPv6 unicast because the Route Distinguisher is
137/// part of the address-family-specific route key.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
139pub enum VpnPrefix {
140    /// VPN-IPv4 prefix.
141    V4 {
142        /// IPv4 network address with host bits zeroed.
143        addr: Ipv4Addr,
144        /// Prefix length in bits.
145        len: u8,
146    },
147    /// VPN-IPv6 prefix.
148    V6 {
149        /// IPv6 network address with host bits zeroed.
150        addr: Ipv6Addr,
151        /// Prefix length in bits.
152        len: u8,
153    },
154}
155
156impl VpnPrefix {
157    /// Create a canonical VPN-IPv4 prefix.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`EncodeError::ValueOutOfRange`] if `len` exceeds 32.
162    pub fn v4(addr: Ipv4Addr, len: u8) -> Result<Self, EncodeError> {
163        if len > 32 {
164            return Err(EncodeError::ValueOutOfRange {
165                field: "VPNv4 prefix length",
166                value: len.to_string(),
167            });
168        }
169        Ok(Self::V4 {
170            addr: Ipv4Addr::from(mask_v4(addr, len)),
171            len,
172        })
173    }
174
175    /// Create a canonical VPN-IPv6 prefix.
176    ///
177    /// # Errors
178    ///
179    /// Returns [`EncodeError::ValueOutOfRange`] if `len` exceeds 128.
180    pub fn v6(addr: Ipv6Addr, len: u8) -> Result<Self, EncodeError> {
181        if len > 128 {
182            return Err(EncodeError::ValueOutOfRange {
183                field: "VPNv6 prefix length",
184                value: len.to_string(),
185            });
186        }
187        Ok(Self::V6 {
188            addr: Ipv6Addr::from(mask_v6(addr, len)),
189            len,
190        })
191    }
192
193    /// Return the VPN address family.
194    #[must_use]
195    pub const fn family(&self) -> VpnAddressFamily {
196        match self {
197            Self::V4 { .. } => VpnAddressFamily::V4,
198            Self::V6 { .. } => VpnAddressFamily::V6,
199        }
200    }
201
202    /// Return the IP prefix length.
203    #[must_use]
204    pub const fn len(&self) -> u8 {
205        match self {
206            Self::V4 { len, .. } | Self::V6 { len, .. } => *len,
207        }
208    }
209
210    /// Return true for a zero-length IP prefix.
211    #[must_use]
212    pub const fn is_empty(&self) -> bool {
213        self.len() == 0
214    }
215
216    fn wire_octets(&self) -> [u8; 16] {
217        match self {
218            Self::V4 { addr, .. } => {
219                let mut out = [0u8; 16];
220                out[..4].copy_from_slice(&addr.octets());
221                out
222            }
223            Self::V6 { addr, .. } => addr.octets(),
224        }
225    }
226}
227
228impl fmt::Display for VpnPrefix {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        match self {
231            Self::V4 { addr, len } => write!(f, "{addr}/{len}"),
232            Self::V6 { addr, len } => write!(f, "{addr}/{len}"),
233        }
234    }
235}
236
237/// Route-key identity for one VPNv4/VPNv6 NLRI.
238///
239/// The label stack is intentionally excluded: labels are route data carried by
240/// the path, while the VPN route key is RD plus IP prefix.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
242pub struct VpnRouteKey {
243    /// Route Distinguisher.
244    pub route_distinguisher: RouteDistinguisher,
245    /// RD-scoped VPN IP prefix.
246    pub prefix: VpnPrefix,
247}
248
249/// One VPNv4/VPNv6 labeled NLRI.
250#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
251pub struct VpnNlri {
252    /// MPLS label stack bound to the VPN prefix.
253    pub labels: Vec<MplsLabelEntry>,
254    /// Route Distinguisher.
255    pub route_distinguisher: RouteDistinguisher,
256    /// RD-scoped VPN IP prefix.
257    pub prefix: VpnPrefix,
258}
259
260impl VpnNlri {
261    /// Return the route-key identity for this NLRI.
262    #[must_use]
263    pub const fn key(&self) -> VpnRouteKey {
264        VpnRouteKey {
265            route_distinguisher: self.route_distinguisher,
266            prefix: self.prefix,
267        }
268    }
269
270    fn validate_for_family(&self, family: VpnAddressFamily) -> Result<(), EncodeError> {
271        if self.prefix.family() != family {
272            return Err(EncodeError::ValueOutOfRange {
273                field: "VPN NLRI family",
274                value: self.prefix.family().to_string(),
275            });
276        }
277        validate_label_stack(&self.labels)?;
278        let total_bits = total_vpn_nlri_bits(self.labels.len(), self.prefix.len());
279        if total_bits > u16::from(u8::MAX) {
280            return Err(EncodeError::ValueOutOfRange {
281                field: "VPN NLRI length bits",
282                value: total_bits.to_string(),
283            });
284        }
285        Ok(())
286    }
287}
288
289/// Decode `VPNv4` NLRI bytes.
290///
291/// # Errors
292///
293/// Returns [`DecodeError`] for malformed length, label stack, RD, or prefix
294/// encodings.
295pub fn decode_vpnv4_nlri(buf: &[u8]) -> Result<Vec<VpnNlri>, DecodeError> {
296    decode_vpn_nlri(buf, VpnAddressFamily::V4)
297}
298
299/// Decode `VPNv6` NLRI bytes.
300///
301/// # Errors
302///
303/// Returns [`DecodeError`] for malformed length, label stack, RD, or prefix
304/// encodings.
305pub fn decode_vpnv6_nlri(buf: &[u8]) -> Result<Vec<VpnNlri>, DecodeError> {
306    decode_vpn_nlri(buf, VpnAddressFamily::V6)
307}
308
309/// Decode `VPNv4` or `VPNv6` NLRI bytes for the selected family.
310///
311/// # Errors
312///
313/// Returns [`DecodeError`] for malformed length, label stack, RD, or prefix
314/// encodings.
315pub fn decode_vpn_nlri(
316    mut buf: &[u8],
317    family: VpnAddressFamily,
318) -> Result<Vec<VpnNlri>, DecodeError> {
319    let mut entries = Vec::new();
320
321    while !buf.is_empty() {
322        let field_start = buf;
323        let total_len_bits = buf[0];
324        buf = &buf[1..];
325        let value_len = usize::from(total_len_bits.div_ceil(8));
326
327        if buf.len() < value_len {
328            return invalid_vpn_nlri(
329                format!(
330                    "{family} NLRI truncated: length {total_len_bits} bits requires {value_len} bytes, have {}",
331                    buf.len()
332                ),
333                field_start,
334                1 + buf.len(),
335            );
336        }
337
338        let value = &buf[..value_len];
339        buf = &buf[value_len..];
340
341        entries.push(decode_one_vpn_nlri(
342            value,
343            total_len_bits,
344            family,
345            field_start,
346        )?);
347    }
348
349    Ok(entries)
350}
351
352/// Encode `VPNv4` NLRI bytes.
353///
354/// # Errors
355///
356/// Returns [`EncodeError`] if an entry is not `VPNv4`, its label stack is
357/// invalid, or its encoded length cannot fit in the one-octet NLRI length.
358pub fn encode_vpnv4_nlri(entries: &[VpnNlri], buf: &mut Vec<u8>) -> Result<(), EncodeError> {
359    encode_vpn_nlri(entries, VpnAddressFamily::V4, buf)
360}
361
362/// Encode `VPNv6` NLRI bytes.
363///
364/// # Errors
365///
366/// Returns [`EncodeError`] if an entry is not `VPNv6`, its label stack is
367/// invalid, or its encoded length cannot fit in the one-octet NLRI length.
368pub fn encode_vpnv6_nlri(entries: &[VpnNlri], buf: &mut Vec<u8>) -> Result<(), EncodeError> {
369    encode_vpn_nlri(entries, VpnAddressFamily::V6, buf)
370}
371
372/// Encode `VPNv4` or `VPNv6` NLRI bytes for the selected family.
373///
374/// On error, `buf` is restored to its original length.
375///
376/// # Errors
377///
378/// Returns [`EncodeError`] if an entry belongs to the wrong family, its label
379/// stack is invalid, or its encoded length cannot fit in the one-octet NLRI
380/// length.
381pub fn encode_vpn_nlri(
382    entries: &[VpnNlri],
383    family: VpnAddressFamily,
384    buf: &mut Vec<u8>,
385) -> Result<(), EncodeError> {
386    let start_len = buf.len();
387
388    for entry in entries {
389        if let Err(err) = encode_one_vpn_nlri(entry, family, buf) {
390            buf.truncate(start_len);
391            return Err(err);
392        }
393    }
394
395    Ok(())
396}
397
398fn decode_one_vpn_nlri(
399    value: &[u8],
400    total_len_bits: u8,
401    family: VpnAddressFamily,
402    field_start: &[u8],
403) -> Result<VpnNlri, DecodeError> {
404    let min_bits = u16::from(MPLS_LABEL_ENTRY_BITS) + u16::from(ROUTE_DISTINGUISHER_BITS);
405    if u16::from(total_len_bits) < min_bits {
406        return invalid_vpn_nlri(
407            format!("{family} NLRI length {total_len_bits} bits is shorter than label+RD"),
408            field_start,
409            1 + value.len(),
410        );
411    }
412
413    let (labels, label_octets, label_bits) =
414        decode_label_stack(value, total_len_bits, family, field_start)?;
415    let rd_offset = label_octets;
416    let rd_end = rd_offset + ROUTE_DISTINGUISHER_LEN;
417    if value.len() < rd_end {
418        return invalid_vpn_nlri(
419            format!("{family} NLRI truncated before Route Distinguisher"),
420            field_start,
421            1 + value.len(),
422        );
423    }
424
425    let mut rd = [0u8; ROUTE_DISTINGUISHER_LEN];
426    rd.copy_from_slice(&value[rd_offset..rd_end]);
427
428    // Reserve the label-stack and RD bits with checked subtraction: a
429    // multi-label stack can consume enough bits that `total_len_bits` no longer
430    // covers the RD, which would underflow this u8 and panic in debug builds
431    // (violating the crate's no-panic-on-malformed-input invariant).
432    let Some(prefix_len) = total_len_bits
433        .checked_sub(label_bits)
434        .and_then(|rem| rem.checked_sub(ROUTE_DISTINGUISHER_BITS))
435    else {
436        return invalid_vpn_nlri(
437            format!(
438                "{family} NLRI length {total_len_bits} bits cannot hold the {label_bits}-bit label stack plus Route Distinguisher"
439            ),
440            field_start,
441            1 + value.len(),
442        );
443    };
444    if prefix_len > family.max_prefix_len() {
445        return invalid_vpn_nlri(
446            format!(
447                "{family} prefix length {prefix_len} exceeds {}",
448                family.max_prefix_len()
449            ),
450            field_start,
451            1 + value.len(),
452        );
453    }
454
455    let prefix_octets = usize::from(prefix_len.div_ceil(8));
456    let prefix_start = rd_end;
457    let prefix_end = prefix_start + prefix_octets;
458    if value.len() < prefix_end {
459        return invalid_vpn_nlri(
460            format!("{family} NLRI truncated before prefix"),
461            field_start,
462            1 + value.len(),
463        );
464    }
465
466    let prefix = decode_vpn_prefix(family, prefix_len, &value[prefix_start..prefix_end])?;
467    Ok(VpnNlri {
468        labels,
469        route_distinguisher: RouteDistinguisher(rd),
470        prefix,
471    })
472}
473
474fn decode_label_stack(
475    value: &[u8],
476    total_len_bits: u8,
477    family: VpnAddressFamily,
478    field_start: &[u8],
479) -> Result<(Vec<MplsLabelEntry>, usize, u8), DecodeError> {
480    let mut labels = Vec::new();
481    let mut offset = 0usize;
482    let mut label_bits = 0u8;
483
484    loop {
485        if u16::from(label_bits) + u16::from(MPLS_LABEL_ENTRY_BITS) > u16::from(total_len_bits) {
486            return invalid_vpn_nlri(
487                format!("{family} NLRI label stack has no bottom-of-stack marker"),
488                field_start,
489                1 + value.len(),
490            );
491        }
492        if value.len() < offset + MPLS_LABEL_ENTRY_LEN {
493            return invalid_vpn_nlri(
494                format!("{family} NLRI truncated inside label stack"),
495                field_start,
496                1 + value.len(),
497            );
498        }
499
500        let raw = (u32::from(value[offset]) << 16)
501            | (u32::from(value[offset + 1]) << 8)
502            | u32::from(value[offset + 2]);
503        let label = MplsLabelEntry::from_raw(raw);
504        labels.push(label);
505        offset += MPLS_LABEL_ENTRY_LEN;
506        label_bits += MPLS_LABEL_ENTRY_BITS;
507
508        if label.bottom_of_stack {
509            return Ok((labels, offset, label_bits));
510        }
511    }
512}
513
514fn decode_vpn_prefix(
515    family: VpnAddressFamily,
516    len: u8,
517    bytes: &[u8],
518) -> Result<VpnPrefix, DecodeError> {
519    let expected = usize::from(len.div_ceil(8));
520    if bytes.len() != expected {
521        return Err(DecodeError::MalformedField {
522            message_type: "UPDATE",
523            detail: format!(
524                "{family} prefix byte length {} != expected {expected}",
525                bytes.len()
526            ),
527        });
528    }
529
530    match family {
531        VpnAddressFamily::V4 => {
532            let mut octets = [0u8; 4];
533            octets[..bytes.len()].copy_from_slice(bytes);
534            Ok(VpnPrefix::V4 {
535                addr: Ipv4Addr::from(mask_v4(Ipv4Addr::from(octets), len)),
536                len,
537            })
538        }
539        VpnAddressFamily::V6 => {
540            let mut octets = [0u8; 16];
541            octets[..bytes.len()].copy_from_slice(bytes);
542            Ok(VpnPrefix::V6 {
543                addr: Ipv6Addr::from(mask_v6(Ipv6Addr::from(octets), len)),
544                len,
545            })
546        }
547    }
548}
549
550fn encode_one_vpn_nlri(
551    entry: &VpnNlri,
552    family: VpnAddressFamily,
553    buf: &mut Vec<u8>,
554) -> Result<(), EncodeError> {
555    entry.validate_for_family(family)?;
556    let total_bits = total_vpn_nlri_bits(entry.labels.len(), entry.prefix.len());
557    let total_bits_u8 = u8::try_from(total_bits).map_err(|_| EncodeError::ValueOutOfRange {
558        field: "VPN NLRI length bits",
559        value: total_bits.to_string(),
560    })?;
561    buf.push(total_bits_u8);
562
563    for label in &entry.labels {
564        let raw = label.raw_value()?;
565        buf.push(((raw >> 16) & 0xFF) as u8);
566        buf.push(((raw >> 8) & 0xFF) as u8);
567        buf.push((raw & 0xFF) as u8);
568    }
569
570    buf.extend_from_slice(&entry.route_distinguisher.0);
571    let prefix_octets = entry.prefix.wire_octets();
572    let prefix_byte_count = usize::from(entry.prefix.len().div_ceil(8));
573    buf.extend_from_slice(&prefix_octets[..prefix_byte_count]);
574    Ok(())
575}
576
577fn validate_label_stack(labels: &[MplsLabelEntry]) -> Result<(), EncodeError> {
578    if labels.is_empty() {
579        return Err(EncodeError::ValueOutOfRange {
580            field: "VPN label stack",
581            value: "empty".to_string(),
582        });
583    }
584    for (index, label) in labels.iter().enumerate() {
585        validate_label(label.label)?;
586        validate_traffic_class(label.traffic_class)?;
587        if label.bottom_of_stack && index + 1 != labels.len() {
588            return Err(EncodeError::ValueOutOfRange {
589                field: "VPN label stack",
590                value: "bottom-of-stack before final label".to_string(),
591            });
592        }
593    }
594    if !labels.last().is_some_and(|label| label.bottom_of_stack) {
595        return Err(EncodeError::ValueOutOfRange {
596            field: "VPN label stack",
597            value: "missing bottom-of-stack".to_string(),
598        });
599    }
600    Ok(())
601}
602
603fn validate_label(label: u32) -> Result<(), EncodeError> {
604    if label > MAX_MPLS_LABEL {
605        return Err(EncodeError::ValueOutOfRange {
606            field: "MPLS label",
607            value: label.to_string(),
608        });
609    }
610    Ok(())
611}
612
613fn validate_traffic_class(traffic_class: u8) -> Result<(), EncodeError> {
614    if traffic_class > MAX_MPLS_TRAFFIC_CLASS {
615        return Err(EncodeError::ValueOutOfRange {
616            field: "MPLS traffic class",
617            value: traffic_class.to_string(),
618        });
619    }
620    Ok(())
621}
622
623fn total_vpn_nlri_bits(label_count: usize, prefix_len: u8) -> u16 {
624    let label_bits = u16::try_from(label_count)
625        .unwrap_or(u16::MAX)
626        .saturating_mul(u16::from(MPLS_LABEL_ENTRY_BITS));
627    // Saturate the final adds too: an extreme label_count saturates label_bits
628    // to u16::MAX, and a plain `+` would then overflow and panic in debug.
629    // Callers reject any total > u8::MAX, so a saturated value is rejected
630    // cleanly downstream.
631    label_bits
632        .saturating_add(u16::from(ROUTE_DISTINGUISHER_BITS))
633        .saturating_add(u16::from(prefix_len))
634}
635
636fn mask_v4(addr: Ipv4Addr, len: u8) -> u32 {
637    let raw = u32::from(addr);
638    if len == 0 {
639        0
640    } else if len >= 32 {
641        raw
642    } else {
643        raw & !((1u32 << (32 - len)) - 1)
644    }
645}
646
647fn mask_v6(addr: Ipv6Addr, len: u8) -> u128 {
648    let raw = u128::from(addr);
649    if len == 0 {
650        0
651    } else if len >= 128 {
652        raw
653    } else {
654        raw & !((1u128 << (128 - len)) - 1)
655    }
656}
657
658fn invalid_vpn_nlri<T>(detail: String, data: &[u8], len: usize) -> Result<T, DecodeError> {
659    Err(DecodeError::InvalidNetworkField {
660        detail,
661        data: data[..len.min(data.len())].to_vec(),
662    })
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668
669    fn rd() -> RouteDistinguisher {
670        RouteDistinguisher([0, 0, 0xFD, 0xE9, 0, 0, 0, 100])
671    }
672
673    fn label(value: u32, bos: bool) -> MplsLabelEntry {
674        MplsLabelEntry::try_new(value, 0, bos).unwrap()
675    }
676
677    #[test]
678    fn constants_match_standards() {
679        assert_eq!(VPNV4_AFI, 1);
680        assert_eq!(VPNV6_AFI, 2);
681        assert_eq!(LABELED_UNICAST_SAFI, 4);
682        assert_eq!(MPLS_VPN_SAFI, 128);
683        assert_eq!(ROUTE_DISTINGUISHER_LEN, 8);
684        assert_eq!(MPLS_LABEL_ENTRY_BITS, 24);
685    }
686
687    #[test]
688    fn label_entry_roundtrip() {
689        let entry = MplsLabelEntry::try_new(100_000, 5, true).unwrap();
690        let raw = entry.raw_value().unwrap();
691        assert_eq!(MplsLabelEntry::from_raw(raw), entry);
692    }
693
694    #[test]
695    fn vpnv4_single_label_roundtrip() {
696        let entry = VpnNlri {
697            labels: vec![label(200, true)],
698            route_distinguisher: rd(),
699            prefix: VpnPrefix::v4(Ipv4Addr::new(10, 0, 1, 99), 24).unwrap(),
700        };
701        let mut buf = Vec::new();
702        encode_vpnv4_nlri(std::slice::from_ref(&entry), &mut buf).unwrap();
703        assert_eq!(buf[0], 24 + 64 + 24);
704
705        let decoded = decode_vpnv4_nlri(&buf).unwrap();
706        assert_eq!(decoded, vec![entry]);
707        assert_eq!(decoded[0].prefix.to_string(), "10.0.1.0/24");
708    }
709
710    #[test]
711    fn vpnv6_two_label_roundtrip() {
712        let prefix = "2001:db8:100::1".parse::<Ipv6Addr>().unwrap();
713        let entry = VpnNlri {
714            labels: vec![label(16_000, false), label(24_000, true)],
715            route_distinguisher: rd(),
716            prefix: VpnPrefix::v6(prefix, 48).unwrap(),
717        };
718        let mut buf = Vec::new();
719        encode_vpnv6_nlri(std::slice::from_ref(&entry), &mut buf).unwrap();
720        assert_eq!(buf[0], 48 + 64 + 48);
721
722        let decoded = decode_vpnv6_nlri(&buf).unwrap();
723        assert_eq!(decoded, vec![entry]);
724        assert_eq!(decoded[0].prefix.to_string(), "2001:db8:100::/48");
725    }
726
727    #[test]
728    fn multiple_nlri_preserve_order() {
729        let a = VpnNlri {
730            labels: vec![label(100, true)],
731            route_distinguisher: rd(),
732            prefix: VpnPrefix::v4(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(),
733        };
734        let b = VpnNlri {
735            labels: vec![label(101, true)],
736            route_distinguisher: rd(),
737            prefix: VpnPrefix::v4(Ipv4Addr::new(192, 0, 2, 0), 24).unwrap(),
738        };
739        let mut buf = Vec::new();
740        encode_vpnv4_nlri(&[a.clone(), b.clone()], &mut buf).unwrap();
741
742        assert_eq!(decode_vpnv4_nlri(&buf).unwrap(), vec![a, b]);
743    }
744
745    #[test]
746    fn route_key_excludes_label_stack() {
747        let prefix = VpnPrefix::v4(Ipv4Addr::new(203, 0, 113, 0), 24).unwrap();
748        let a = VpnNlri {
749            labels: vec![label(100, true)],
750            route_distinguisher: rd(),
751            prefix,
752        };
753        let b = VpnNlri {
754            labels: vec![label(200, true)],
755            route_distinguisher: rd(),
756            prefix,
757        };
758
759        assert_eq!(a.key(), b.key());
760        assert_ne!(a, b);
761    }
762
763    #[test]
764    fn decode_rejects_nlri_shorter_than_label_plus_rd() {
765        let err = decode_vpnv4_nlri(&[87, 0, 0, 1]).unwrap_err();
766        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
767    }
768
769    #[test]
770    fn decode_rejects_truncated_value() {
771        let err = decode_vpnv4_nlri(&[112, 0, 0x0C, 0x81]).unwrap_err();
772        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
773    }
774
775    #[test]
776    fn decode_rejects_missing_bottom_of_stack() {
777        let mut buf = vec![24 + 64, 0, 0x0C, 0x80];
778        buf.extend_from_slice(&rd().0);
779        let err = decode_vpnv4_nlri(&buf).unwrap_err();
780        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
781    }
782
783    #[test]
784    fn decode_rejects_label_stack_consuming_rd_bits_without_underflow() {
785        // total_len_bits = 105 with a two-label stack (48 label bits) leaves
786        // only 57 bits — fewer than the 64 Route Distinguisher bits — so the
787        // prefix-length computation would underflow u8 and panic in debug.
788        // The decoder must reject cleanly instead. Bytes: len=105, label1
789        // (no BoS), label2 (BoS set), then the 8-byte RD.
790        let mut buf = vec![105u8, 0, 0, 0, 0, 0, 1];
791        buf.extend_from_slice(&rd().0);
792        let err = decode_vpnv4_nlri(&buf).unwrap_err();
793        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
794    }
795
796    #[test]
797    fn decode_rejects_high_bit_length_without_bottom_of_stack() {
798        let mut buf = vec![u8::MAX];
799        for value in 0..10u32 {
800            let raw = MplsLabelEntry::try_new(value + 100, 0, false)
801                .unwrap()
802                .raw_value()
803                .unwrap();
804            buf.push(((raw >> 16) & 0xFF) as u8);
805            buf.push(((raw >> 8) & 0xFF) as u8);
806            buf.push((raw & 0xFF) as u8);
807        }
808        buf.extend_from_slice(&[0, 0]);
809
810        let err = decode_vpnv6_nlri(&buf).unwrap_err();
811        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
812    }
813
814    #[test]
815    fn decode_rejects_prefix_too_long_for_family() {
816        let mut buf = vec![24 + 64 + 33, 0, 0x0C, 0x81];
817        buf.extend_from_slice(&rd().0);
818        buf.extend_from_slice(&[10, 0, 0, 0, 0]);
819        let err = decode_vpnv4_nlri(&buf).unwrap_err();
820        assert!(matches!(err, DecodeError::InvalidNetworkField { .. }));
821    }
822
823    #[test]
824    fn encode_rejects_empty_label_stack_and_restores_buffer() {
825        let entry = VpnNlri {
826            labels: vec![],
827            route_distinguisher: rd(),
828            prefix: VpnPrefix::v4(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(),
829        };
830        let mut buf = vec![0xAA];
831        let err = encode_vpnv4_nlri(&[entry], &mut buf).unwrap_err();
832
833        assert!(matches!(err, EncodeError::ValueOutOfRange { .. }));
834        assert_eq!(buf, vec![0xAA]);
835    }
836
837    #[test]
838    fn encode_rejects_missing_bottom_of_stack() {
839        let entry = VpnNlri {
840            labels: vec![label(100, false)],
841            route_distinguisher: rd(),
842            prefix: VpnPrefix::v4(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(),
843        };
844
845        assert!(matches!(
846            encode_vpnv4_nlri(&[entry], &mut Vec::new()),
847            Err(EncodeError::ValueOutOfRange { .. })
848        ));
849    }
850
851    #[test]
852    fn encode_rejects_early_bottom_of_stack() {
853        let entry = VpnNlri {
854            labels: vec![label(100, true), label(200, true)],
855            route_distinguisher: rd(),
856            prefix: VpnPrefix::v4(Ipv4Addr::new(10, 0, 0, 0), 8).unwrap(),
857        };
858
859        assert!(matches!(
860            encode_vpnv4_nlri(&[entry], &mut Vec::new()),
861            Err(EncodeError::ValueOutOfRange { .. })
862        ));
863    }
864
865    #[test]
866    fn encode_rejects_label_and_traffic_class_out_of_range() {
867        assert!(MplsLabelEntry::try_new(MAX_MPLS_LABEL + 1, 0, true).is_err());
868        assert!(MplsLabelEntry::try_new(100, MAX_MPLS_TRAFFIC_CLASS + 1, true).is_err());
869    }
870
871    #[test]
872    fn encode_rejects_too_long_vpnv6_nlri() {
873        let entry = VpnNlri {
874            labels: vec![label(100, false), label(200, false), label(300, true)],
875            route_distinguisher: rd(),
876            prefix: VpnPrefix::v6(Ipv6Addr::UNSPECIFIED, 128).unwrap(),
877        };
878
879        let err = encode_vpnv6_nlri(&[entry], &mut Vec::new()).unwrap_err();
880        assert!(matches!(err, EncodeError::ValueOutOfRange { .. }));
881    }
882
883    #[test]
884    fn encode_rejects_wrong_family() {
885        let entry = VpnNlri {
886            labels: vec![label(100, true)],
887            route_distinguisher: rd(),
888            prefix: VpnPrefix::v6(Ipv6Addr::LOCALHOST, 128).unwrap(),
889        };
890
891        let err = encode_vpnv4_nlri(&[entry], &mut Vec::new()).unwrap_err();
892        assert!(matches!(err, EncodeError::ValueOutOfRange { .. }));
893    }
894}