Skip to main content

bgpkit_parser/models/network/
mpls.rs

1//! MPLS Labeled NLRI support - RFC 3107 and RFC 8277
2//!
3//! This module provides support for parsing and encoding MPLS-labeled BGP NLRI
4//! as specified in RFC 3107 (Carrying Label Information in BGP-4) and its
5//! successor RFC 8277 (Using BGP to Bind MPLS Labels to Address Prefixes).
6//!
7//! ## RFC 8277 Modes
8//!
9//! RFC 8277 defines two distinct NLRI encoding modes:
10//!
11//! - **SingleLabel** (§2.2): Used when Multiple Labels Capability is not negotiated.
12//!   Exactly one label is encoded, and the S (Bottom-of-Stack) bit MUST be ignored
13//!   on reception.
14//!
15//! - **MultiLabel** (§2.3): Used when Multiple Labels Capability (Code 8) is negotiated.
16//!   Multiple labels can be encoded with the BoS bit delimiting the stack.
17
18use crate::models::network::{Afi, NetworkPrefix};
19#[cfg(feature = "parser")]
20use bytes::{Buf, Bytes};
21use ipnet::IpNet;
22use smallvec::SmallVec;
23use std::fmt::{Debug, Formatter};
24
25/// MPLS Label value (20-bit, 0-1,048,575)
26#[derive(PartialEq, Eq, Clone, Copy, Hash)]
27pub struct MplsLabel(u32);
28
29impl MplsLabel {
30    /// Maximum valid label value (20-bit mask)
31    pub const MAX_VALUE: u32 = 0x000F_FFFF;
32
33    /// IPv4 Explicit NULL label (RFC 3032)
34    pub const IPV4_EXPLICIT_NULL: u32 = 0;
35    /// IPv6 Explicit NULL label (RFC 3032)
36    pub const IPV6_EXPLICIT_NULL: u32 = 2;
37    /// Implicit NULL label (RFC 3032)
38    pub const IMPLICIT_NULL: u32 = 3;
39
40    /// Create a new MplsLabel with validation.
41    /// Returns Err if value exceeds 20-bit range.
42    pub fn try_new(value: u32) -> Result<Self, MplsLabelError> {
43        if value > Self::MAX_VALUE {
44            return Err(MplsLabelError::LabelValueTooLarge(value));
45        }
46        Ok(Self(value))
47    }
48
49    /// Create a new MplsLabel from a value that is already known to be valid.
50    /// Used internally when decoding from wire format.
51    pub(crate) fn new_masked(value: u32) -> Self {
52        Self(value & Self::MAX_VALUE)
53    }
54
55    /// Get the label value (0..=0xFFFFF)
56    pub fn value(&self) -> u32 {
57        self.0
58    }
59
60    /// Check if label is in reserved range (0-15 per RFC 3032)
61    pub fn is_reserved(&self) -> bool {
62        self.0 <= 15
63    }
64
65    /// Check if label is Implicit NULL (value 3)
66    pub fn is_implicit_null(&self) -> bool {
67        self.0 == Self::IMPLICIT_NULL
68    }
69
70    /// Check if label is IPv4 Explicit NULL (value 0)
71    pub fn is_ipv4_explicit_null(&self) -> bool {
72        self.0 == Self::IPV4_EXPLICIT_NULL
73    }
74
75    /// Check if label is IPv6 Explicit NULL (value 2)
76    pub fn is_ipv6_explicit_null(&self) -> bool {
77        self.0 == Self::IPV6_EXPLICIT_NULL
78    }
79
80    /// Encode label to 3-byte wire format per RFC 3032.
81    ///
82    /// Wire format: bits 23-4 = label value, bits 3-1 = reserved (0), bit 0 = Bottom-of-Stack
83    pub fn encode(&self, is_bottom: bool) -> [u8; 3] {
84        let raw = (self.0 << 4) | (if is_bottom { 1 } else { 0 });
85        [(raw >> 16) as u8, (raw >> 8) as u8, raw as u8]
86    }
87
88    /// Decode label from 3-byte wire format per RFC 3032.
89    ///
90    /// Returns (label, bottom_of_stack_flag).
91    /// Note: Reserved bits (3-1) are ignored.
92    pub fn decode(bytes: [u8; 3]) -> (Self, bool) {
93        let raw = ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32);
94        let label_value = raw >> 4;
95        let bos = (raw & 0x01) != 0;
96        (Self::new_masked(label_value), bos)
97    }
98}
99
100impl Debug for MplsLabel {
101    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102        write!(f, "MplsLabel({})", self.0)
103    }
104}
105
106#[cfg(feature = "serde")]
107impl serde::Serialize for MplsLabel {
108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109    where
110        S: serde::Serializer,
111    {
112        serializer.serialize_u32(self.0)
113    }
114}
115
116#[cfg(feature = "serde")]
117impl<'de> serde::Deserialize<'de> for MplsLabel {
118    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
119    where
120        D: serde::Deserializer<'de>,
121    {
122        let value = u32::deserialize(deserializer)?;
123        Self::try_new(value).map_err(serde::de::Error::custom)
124    }
125}
126
127/// Error type for MplsLabel construction
128#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum MplsLabelError {
130    LabelValueTooLarge(u32),
131}
132
133impl std::fmt::Display for MplsLabelError {
134    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
135        match self {
136            MplsLabelError::LabelValueTooLarge(v) => {
137                write!(
138                    f,
139                    "MPLS label value {} exceeds maximum 0x{:X}",
140                    v,
141                    MplsLabel::MAX_VALUE
142                )
143            }
144        }
145    }
146}
147
148impl std::error::Error for MplsLabelError {}
149
150/// RFC 8277 parsing mode for labeled NLRI
151///
152/// RFC 8277 defines two distinct encoding modes that are NOT compatible on the wire:
153///
154/// - **SingleLabel** (§2.2): No Multiple Labels Capability negotiated. Exactly one label,
155///   S-bit MUST be ignored on reception.
156///
157/// - **MultiLabel** (§2.3): Multiple Labels Capability negotiated. Multiple labels allowed,
158///   use BoS bit to delimit stack.
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161pub enum LabeledNlriMode {
162    /// RFC 8277 §2.2: Single-label encoding (no Multiple Labels Capability negotiated).
163    /// In this mode, exactly one label is expected and the S-bit is ignored.
164    /// Note: Using this mode with multi-label data will result in parse errors.
165    SingleLabel,
166    /// RFC 8277 §2.3: Multi-label encoding (Multiple Labels Capability negotiated).
167    /// This is the default as it correctly handles both single-label and multi-label prefixes.
168    #[default]
169    MultiLabel,
170}
171
172/// Configuration for parsing labeled NLRI
173#[derive(Debug, Clone, PartialEq, Eq)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
175pub struct LabeledNlriConfig {
176    /// ADD-PATH enabled (RFC 7911). When true, parse 4-byte path_id before each NLRI.
177    ///
178    /// **CRITICAL**: ADD-PATH cannot be autodetected from NLRI bytes alone. If ADD-PATH
179    /// is present on wire but this is false, the path_id bytes will be misinterpreted
180    /// as NLRI length, causing complete stream desynchronization. The caller MUST
181    /// configure this correctly based on session state.
182    pub add_path: bool,
183
184    /// RFC 8277 parsing mode (§2.2 SingleLabel vs §2.3 MultiLabel)
185    pub mode: LabeledNlriMode,
186
187    /// Maximum label stack depth for DoS protection.
188    /// RFC 8277 allows up to 254 (255 means unlimited).
189    /// Range: 1..=254
190    pub max_labels: u8,
191
192    /// Peer-negotiated maximum labels from Multiple Labels Capability.
193    /// If set and `mode` is MultiLabel, enforce this limit per RFC 8277 §2.1.
194    /// Receiving more labels than advertised produces a treat-as-withdraw error.
195    /// None means no peer limit (use local `max_labels` only).
196    pub peer_max_labels: Option<u8>,
197}
198
199impl LabeledNlriConfig {
200    /// Create a new config with validation.
201    /// Returns Err if max_labels is 0 or >254, or if peer_max_labels is Some(0).
202    /// Note: per RFC 8277 §2.1, peer_max_labels of 1 is accepted (only 0 is forbidden).
203    pub fn try_new(
204        add_path: bool,
205        mode: LabeledNlriMode,
206        max_labels: u8,
207        peer_max_labels: Option<u8>,
208    ) -> Result<Self, LabeledNlriConfigError> {
209        if max_labels == 0 || max_labels > 254 {
210            return Err(LabeledNlriConfigError::InvalidMaxLabels(max_labels));
211        }
212        if let Some(peer) = peer_max_labels {
213            if peer == 0 {
214                return Err(LabeledNlriConfigError::InvalidPeerMaxLabels(peer));
215            }
216        }
217        Ok(Self {
218            add_path,
219            mode,
220            max_labels,
221            peer_max_labels,
222        })
223    }
224}
225
226impl Default for LabeledNlriConfig {
227    fn default() -> Self {
228        Self {
229            add_path: false,
230            mode: LabeledNlriMode::default(),
231            max_labels: 16,
232            peer_max_labels: None,
233        }
234    }
235}
236
237/// Error type for LabeledNlriConfig construction
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub enum LabeledNlriConfigError {
240    InvalidMaxLabels(u8),
241    InvalidPeerMaxLabels(u8),
242}
243
244impl std::fmt::Display for LabeledNlriConfigError {
245    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
246        match self {
247            LabeledNlriConfigError::InvalidMaxLabels(v) => {
248                write!(f, "max_labels {} is invalid, must be 1-254", v)
249            }
250            LabeledNlriConfigError::InvalidPeerMaxLabels(v) => {
251                write!(f, "peer_max_labels {} is invalid, must be 2-254 or None", v)
252            }
253        }
254    }
255}
256
257impl std::error::Error for LabeledNlriConfigError {}
258
259/// A network prefix with MPLS labels (RFC 3107/8277)
260#[derive(Debug, PartialEq, Clone, Eq)]
261#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
262pub struct LabeledNetworkPrefix {
263    /// The IP prefix (IPv4 or IPv6)
264    pub prefix: IpNet,
265    /// MPLS label stack, ordered from top to bottom
266    /// Uses SmallVec to avoid heap allocations for the common 1-2 label case
267    pub labels: SmallVec<[MplsLabel; 2]>,
268    /// ADD-PATH path identifier (RFC 7911)
269    pub path_id: Option<u32>,
270}
271
272/// Error type for LabeledNetworkPrefix construction
273#[derive(Debug, Clone, PartialEq, Eq)]
274pub enum LabeledNetworkPrefixError {
275    EmptyLabelStack,
276    PrefixLengthOverflow { total_bits: usize, max: usize },
277}
278
279impl std::fmt::Display for LabeledNetworkPrefixError {
280    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
281        match self {
282            LabeledNetworkPrefixError::EmptyLabelStack => {
283                write!(f, "labeled prefix must have at least one label")
284            }
285            LabeledNetworkPrefixError::PrefixLengthOverflow { total_bits, max } => {
286                write!(
287                    f,
288                    "total NLRI length {} bits exceeds maximum {} bits",
289                    total_bits, max
290                )
291            }
292        }
293    }
294}
295
296impl std::error::Error for LabeledNetworkPrefixError {}
297
298impl LabeledNetworkPrefix {
299    /// Create a new labeled prefix with validation.
300    /// Returns Err if labels is empty or if total length exceeds 255 bits.
301    pub fn try_new(
302        prefix: IpNet,
303        labels: SmallVec<[MplsLabel; 2]>,
304        path_id: Option<u32>,
305    ) -> Result<Self, LabeledNetworkPrefixError> {
306        if labels.is_empty() {
307            return Err(LabeledNetworkPrefixError::EmptyLabelStack);
308        }
309
310        // Validate total length fits in u8 (0-255 bits per RFC 4760)
311        // Using checked arithmetic to prevent overflow
312        let label_bits = labels.len().checked_mul(24).ok_or(
313            LabeledNetworkPrefixError::PrefixLengthOverflow {
314                total_bits: usize::MAX,
315                max: 255,
316            },
317        )?;
318        let prefix_bits = prefix.prefix_len() as usize;
319        let total_bits = label_bits.checked_add(prefix_bits).ok_or(
320            LabeledNetworkPrefixError::PrefixLengthOverflow {
321                total_bits: usize::MAX,
322                max: 255,
323            },
324        )?;
325
326        if total_bits > 255 {
327            return Err(LabeledNetworkPrefixError::PrefixLengthOverflow {
328                total_bits,
329                max: 255,
330            });
331        }
332
333        Ok(Self {
334            prefix,
335            labels,
336            path_id,
337        })
338    }
339
340    /// Get the top label (first in the stack)
341    pub fn top_label(&self) -> Option<&MplsLabel> {
342        self.labels.first()
343    }
344
345    /// Get the bottom label (last in the stack)
346    pub fn bottom_label(&self) -> Option<&MplsLabel> {
347        self.labels.last()
348    }
349
350    /// Check if the prefix has multiple labels
351    pub fn has_multiple_labels(&self) -> bool {
352        self.labels.len() > 1
353    }
354
355    /// Get the number of labels
356    pub fn label_count(&self) -> usize {
357        self.labels.len()
358    }
359}
360
361/// Error type for labeled NLRI encoding
362#[derive(Debug, Clone, PartialEq, Eq)]
363pub enum LabeledNlriEncodeError {
364    EmptyLabelStack,
365    TotalBitsOverflow {
366        total_bits: usize,
367        max: usize,
368    },
369    SingleLabelModeWithMultipleLabels {
370        label_count: usize,
371    },
372    LabelCountExceedsPeerLimit {
373        actual: usize,
374        peer_max: u8,
375    },
376    /// ADD-PATH capability was not negotiated but path_id is present
377    AddPathNotNegotiated,
378}
379
380impl std::fmt::Display for LabeledNlriEncodeError {
381    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
382        match self {
383            LabeledNlriEncodeError::EmptyLabelStack => {
384                write!(f, "cannot encode labeled prefix with empty label stack")
385            }
386            LabeledNlriEncodeError::TotalBitsOverflow { total_bits, max } => {
387                write!(
388                    f,
389                    "total NLRI length {} bits exceeds maximum {} bits",
390                    total_bits, max
391                )
392            }
393            LabeledNlriEncodeError::SingleLabelModeWithMultipleLabels { label_count } => {
394                write!(f, "single-label mode cannot encode {} labels", label_count)
395            }
396            LabeledNlriEncodeError::LabelCountExceedsPeerLimit { actual, peer_max } => {
397                write!(f, "label count {} exceeds peer limit {}", actual, peer_max)
398            }
399            LabeledNlriEncodeError::AddPathNotNegotiated => {
400                write!(f, "ADD-PATH not negotiated but path_id is present")
401            }
402        }
403    }
404}
405
406impl std::error::Error for LabeledNlriEncodeError {}
407
408#[cfg(feature = "parser")]
409/// Parse labeled NLRI from MP_REACH_NLRI (announcements) per RFC 8277.
410///
411/// This function handles both §2.2 SingleLabel mode (ignore S-bit) and
412/// §2.3 MultiLabel mode (use BoS bit to delimit stack).
413pub fn parse_labeled_nlri(
414    input: &mut Bytes,
415    afi: Afi,
416    config: &LabeledNlriConfig,
417) -> Result<Vec<LabeledNetworkPrefix>, crate::error::ParserError> {
418    use crate::error::ParserError;
419
420    let mut result = Vec::new();
421
422    while input.has_remaining() {
423        // 1. Parse path_id if ADD-PATH enabled (RFC 7911)
424        let path_id = if config.add_path {
425            if input.remaining() < 4 {
426                return Err(ParserError::TruncatedLabeledNlri);
427            }
428            Some(input.get_u32())
429        } else {
430            None
431        };
432
433        // 2. Parse total length field (1 byte, 0-255 bits per RFC 4760)
434        if input.remaining() < 1 {
435            return Err(ParserError::TruncatedLabeledNlri);
436        }
437        let total_bits = input.get_u8() as usize;
438
439        // Validation: total_bits must be at least 24 (minimum for one label)
440        if total_bits < 24 {
441            return Err(ParserError::InvalidLabeledNlriLength);
442        }
443
444        // 3. CRITICAL: Calculate NLRI byte boundary and slice the buffer
445        // This prevents reading beyond the declared NLRI into the next one
446        let nlri_bytes = total_bits.div_ceil(8);
447        if input.remaining() < nlri_bytes {
448            return Err(ParserError::TruncatedLabeledNlri);
449        }
450
451        // Create a bounded view of just this NLRI's bytes
452        let nlri_data = input.copy_to_bytes(nlri_bytes);
453        let mut nlri_input = nlri_data;
454
455        // 4. Parse labels based on mode
456        let mut labels: SmallVec<[MplsLabel; 2]> = SmallVec::new();
457
458        match config.mode {
459            LabeledNlriMode::SingleLabel => {
460                // RFC 8277 §2.2: Exactly one label, S-bit MUST be ignored
461                if nlri_input.remaining() < 3 {
462                    return Err(ParserError::TruncatedLabeledNlri);
463                }
464                let label_bytes = [
465                    nlri_input.get_u8(),
466                    nlri_input.get_u8(),
467                    nlri_input.get_u8(),
468                ];
469                // Decode to get label value, but IGNORE the BoS bit per §2.2
470                let (label, _bos) = MplsLabel::decode(label_bytes);
471                labels.push(label);
472            }
473
474            LabeledNlriMode::MultiLabel => {
475                // RFC 8277 §2.3: Read labels until BoS=1
476                loop {
477                    // DoS protection: enforce max label stack depth
478                    if labels.len() >= config.max_labels as usize {
479                        return Err(ParserError::MaxLabelStackDepthExceeded);
480                    }
481
482                    // Check peer limit if configured
483                    if let Some(peer_max) = config.peer_max_labels {
484                        if labels.len() >= peer_max as usize {
485                            return Err(ParserError::PeerMaxLabelsExceeded);
486                        }
487                    }
488
489                    if nlri_input.remaining() < 3 {
490                        return Err(ParserError::TruncatedLabeledNlri);
491                    }
492
493                    let label_bytes = [
494                        nlri_input.get_u8(),
495                        nlri_input.get_u8(),
496                        nlri_input.get_u8(),
497                    ];
498                    let (label, bos) = MplsLabel::decode(label_bytes);
499                    labels.push(label);
500
501                    if bos {
502                        break;
503                    }
504                }
505            }
506        }
507
508        // 5. Calculate and validate prefix length
509        let label_bits = labels
510            .len()
511            .checked_mul(24)
512            .ok_or(ParserError::InvalidLabeledNlriLength)?;
513
514        // Use checked_sub to prevent integer underflow
515        let prefix_bits = total_bits
516            .checked_sub(label_bits)
517            .ok_or(ParserError::InvalidLabeledNlriLength)?;
518
519        // Validate prefix_bits against AFI-specific maximums
520        let max_prefix_bits = match afi {
521            Afi::Ipv4 => 32,
522            Afi::Ipv6 => 128,
523            _ => return Err(ParserError::InvalidLabeledNlriLength),
524        };
525
526        if prefix_bits > max_prefix_bits {
527            return Err(ParserError::InvalidLabeledNlriLength);
528        }
529
530        // 6. Parse prefix bytes from bounded buffer
531        let prefix_bytes = prefix_bits.div_ceil(8);
532
533        if nlri_input.remaining() < prefix_bytes {
534            return Err(ParserError::TruncatedPrefix);
535        }
536
537        let prefix_data = nlri_input.copy_to_bytes(prefix_bytes);
538        let prefix = parse_prefix_with_masking(afi, &prefix_data, prefix_bits as u8)?;
539
540        // 7. Verify all NLRI bytes were consumed (sanity check)
541        if nlri_input.has_remaining() {
542            return Err(ParserError::InvalidLabeledNlriLength);
543        }
544
545        // 8. Create result
546        result.push(LabeledNetworkPrefix {
547            prefix,
548            labels,
549            path_id,
550        });
551    }
552
553    Ok(result)
554}
555
556#[cfg(feature = "parser")]
557/// Parse prefix with trailing bit masking per RFC 4760.
558///
559/// Uses stack-allocated arrays to avoid heap allocations on the hot path.
560fn parse_prefix_with_masking(
561    afi: Afi,
562    data: &[u8],
563    prefix_bits: u8,
564) -> Result<IpNet, crate::error::ParserError> {
565    use crate::error::ParserError;
566    use std::net::{Ipv4Addr, Ipv6Addr};
567
568    let full_bytes = (prefix_bits as usize) / 8;
569    let remainder_bits = prefix_bits % 8;
570
571    match afi {
572        Afi::Ipv4 => {
573            let mut octets = [0u8; 4];
574            let copy_len = data.len().min(4);
575            octets[..copy_len].copy_from_slice(&data[..copy_len]);
576
577            // Mask trailing bits in the last partial byte
578            if remainder_bits > 0 && copy_len > full_bytes {
579                let mask = 0xFF << (8 - remainder_bits);
580                octets[full_bytes] &= mask;
581            }
582
583            let addr = Ipv4Addr::from(octets);
584            Ok(IpNet::V4(
585                ipnet::Ipv4Net::new(addr, prefix_bits).map_err(|_| ParserError::InvalidPrefix)?,
586            ))
587        }
588        Afi::Ipv6 => {
589            let mut octets = [0u8; 16];
590            let copy_len = data.len().min(16);
591            octets[..copy_len].copy_from_slice(&data[..copy_len]);
592
593            // Mask trailing bits in the last partial byte
594            if remainder_bits > 0 && copy_len > full_bytes {
595                let mask = 0xFF << (8 - remainder_bits);
596                octets[full_bytes] &= mask;
597            }
598
599            let addr = Ipv6Addr::from(octets);
600            Ok(IpNet::V6(
601                ipnet::Ipv6Net::new(addr, prefix_bits).map_err(|_| ParserError::InvalidPrefix)?,
602            ))
603        }
604        _ => Err(ParserError::InvalidLabeledNlriLength),
605    }
606}
607
608#[cfg(feature = "parser")]
609/// Parse labeled withdrawal NLRI from MP_UNREACH_NLRI per RFC 8277 §2.4.
610///
611/// Withdrawals for SAFI 4 are parsed into standard `NetworkPrefix` (not `LabeledNetworkPrefix`)
612/// because RFC 8277 withdrawals carry no label semantics - the 3-byte compatibility field is opaque.
613pub fn parse_labeled_withdrawal_nlri(
614    input: &mut Bytes,
615    afi: Afi,
616    config: &LabeledNlriConfig,
617) -> Result<Vec<NetworkPrefix>, crate::error::ParserError> {
618    use crate::error::ParserError;
619
620    let mut result = Vec::new();
621
622    // Handle empty MP_UNREACH_NLRI (End-of-RIB marker)
623    if !input.has_remaining() {
624        return Ok(result);
625    }
626
627    while input.has_remaining() {
628        // 1. Parse path_id if ADD-PATH enabled (RFC 7911)
629        let path_id = if config.add_path {
630            if input.remaining() < 4 {
631                return Err(ParserError::TruncatedLabeledNlri);
632            }
633            Some(input.get_u32())
634        } else {
635            None
636        };
637
638        // 2. Parse total length field
639        if input.remaining() < 1 {
640            return Err(ParserError::TruncatedLabeledNlri);
641        }
642        let total_bits = input.get_u8() as usize;
643
644        // Validation: RFC 8277 §2.4 requires the 3-byte compatibility field
645        if total_bits < 24 {
646            return Err(ParserError::InvalidLabeledNlriLength);
647        }
648
649        // 3. CRITICAL: Calculate and bound NLRI bytes
650        let nlri_bytes = total_bits.div_ceil(8);
651        if input.remaining() < nlri_bytes {
652            return Err(ParserError::TruncatedLabeledNlri);
653        }
654
655        let nlri_data = input.copy_to_bytes(nlri_bytes);
656        let mut nlri_input = nlri_data;
657
658        // 4. Skip 3-byte compatibility field (opaque, NOT a label)
659        // Per RFC 8277 §2.4: "MUST be ignored on reception"
660        if nlri_input.remaining() < 3 {
661            return Err(ParserError::TruncatedLabeledNlri);
662        }
663        let _compatibility_field = [
664            nlri_input.get_u8(),
665            nlri_input.get_u8(),
666            nlri_input.get_u8(),
667        ];
668
669        // 5. Calculate prefix length using checked arithmetic
670        let prefix_bits = total_bits
671            .checked_sub(24) // 24 bits for the compatibility field
672            .ok_or(ParserError::InvalidLabeledNlriLength)?;
673
674        // Validate prefix_bits against AFI-specific maximums
675        let max_prefix_bits = match afi {
676            Afi::Ipv4 => 32,
677            Afi::Ipv6 => 128,
678            _ => return Err(ParserError::InvalidLabeledNlriLength),
679        };
680
681        if prefix_bits > max_prefix_bits {
682            return Err(ParserError::InvalidLabeledNlriLength);
683        }
684
685        // 6. Parse prefix bytes from bounded buffer
686        let prefix_bytes = prefix_bits.div_ceil(8);
687
688        if nlri_input.remaining() < prefix_bytes {
689            return Err(ParserError::TruncatedPrefix);
690        }
691
692        let prefix_data = nlri_input.copy_to_bytes(prefix_bytes);
693        let prefix = parse_prefix_with_masking(afi, &prefix_data, prefix_bits as u8)?;
694
695        // Verify all NLRI bytes were consumed
696        if nlri_input.has_remaining() {
697            return Err(ParserError::InvalidLabeledNlriLength);
698        }
699
700        // 7. Create result as plain NetworkPrefix (withdrawals have no label semantics)
701        result.push(NetworkPrefix::new(prefix, path_id));
702    }
703
704    Ok(result)
705}
706
707/// Encode a labeled prefix for MP_REACH_NLRI per RFC 8277.
708///
709/// This function respects the encoding mode:
710/// - SingleLabel mode: Exactly one label, BoS=1
711/// - MultiLabel mode: All labels with proper BoS bits
712///
713/// # Arguments
714///
715/// * `prefix` - The labeled prefix to encode
716/// * `mode` - The RFC 8277 encoding mode (SingleLabel or MultiLabel)
717/// * `add_path` - Whether ADD-PATH capability was negotiated (required for path_id encoding)
718/// * `peer_max_labels` - Optional peer-negotiated maximum labels
719///
720/// # Errors
721///
722/// Returns error if:
723/// - Label stack is empty
724/// - Total NLRI length exceeds 255 bits
725/// - SingleLabel mode with multiple labels
726/// - ADD-PATH not negotiated but path_id is present
727/// - Label count exceeds peer_max_labels
728pub fn encode_labeled_prefix(
729    prefix: &LabeledNetworkPrefix,
730    mode: LabeledNlriMode,
731    add_path: bool,
732    peer_max_labels: Option<u8>,
733) -> Result<Vec<u8>, LabeledNlriEncodeError> {
734    // 1. Validate non-empty label stack
735    if prefix.labels.is_empty() {
736        return Err(LabeledNlriEncodeError::EmptyLabelStack);
737    }
738
739    // Check ADD-PATH capability before encoding path_id
740    if prefix.path_id.is_some() && !add_path {
741        return Err(LabeledNlriEncodeError::AddPathNotNegotiated);
742    }
743
744    let mut output = Vec::new();
745
746    // 2. Write path_id if present (only when ADD-PATH is negotiated)
747    if let Some(path_id) = prefix.path_id {
748        output.extend_from_slice(&path_id.to_be_bytes());
749    }
750
751    // 3. Calculate total length in bits using checked arithmetic
752    let label_bits =
753        prefix
754            .labels
755            .len()
756            .checked_mul(24)
757            .ok_or(LabeledNlriEncodeError::TotalBitsOverflow {
758                total_bits: usize::MAX,
759                max: 255,
760            })?;
761    let prefix_bits = prefix.prefix.prefix_len() as usize;
762    let total_bits =
763        label_bits
764            .checked_add(prefix_bits)
765            .ok_or(LabeledNlriEncodeError::TotalBitsOverflow {
766                total_bits: usize::MAX,
767                max: 255,
768            })?;
769
770    // Validate: total_bits must fit in u8 (0-255) per RFC 4760
771    if total_bits > 255 {
772        return Err(LabeledNlriEncodeError::TotalBitsOverflow {
773            total_bits,
774            max: 255,
775        });
776    }
777
778    output.push(total_bits as u8);
779
780    // 4. Encode labels based on mode
781    match mode {
782        LabeledNlriMode::SingleLabel => {
783            // RFC 8277 §2.2: Exactly one label, BoS bit SHOULD be 1
784            if prefix.labels.len() > 1 {
785                return Err(LabeledNlriEncodeError::SingleLabelModeWithMultipleLabels {
786                    label_count: prefix.labels.len(),
787                });
788            }
789            let label = &prefix.labels[0];
790            let encoded = label.encode(true); // BoS=1
791            output.extend_from_slice(&encoded);
792        }
793
794        LabeledNlriMode::MultiLabel => {
795            // RFC 8277 §2.3: Encode all labels with proper BoS bits
796            // Check peer limit if configured
797            if let Some(peer_max) = peer_max_labels {
798                if prefix.labels.len() > peer_max as usize {
799                    return Err(LabeledNlriEncodeError::LabelCountExceedsPeerLimit {
800                        actual: prefix.labels.len(),
801                        peer_max,
802                    });
803                }
804            }
805
806            for (i, label) in prefix.labels.iter().enumerate() {
807                let is_bottom = i == prefix.labels.len() - 1;
808                let encoded = label.encode(is_bottom);
809                output.extend_from_slice(&encoded);
810            }
811        }
812    }
813
814    // 5. Write prefix bytes (truncated to prefix_bits)
815    let prefix_bytes = prefix_bits.div_ceil(8);
816    let prefix_octets = match prefix.prefix {
817        IpNet::V4(p) => p.addr().octets().to_vec(),
818        IpNet::V6(p) => p.addr().octets().to_vec(),
819    };
820    output.extend_from_slice(&prefix_octets[..prefix_bytes]);
821
822    Ok(output)
823}
824
825/// Encode a labeled withdrawal for MP_UNREACH_NLRI per RFC 8277 §2.4.
826///
827/// The 3-byte compatibility field is opaque and SHOULD be 0x800000.
828pub fn encode_labeled_withdrawal(
829    prefix: &NetworkPrefix,
830) -> Result<Vec<u8>, LabeledNlriEncodeError> {
831    let mut output = Vec::new();
832
833    // 1. Write path_id if present (ADD-PATH, RFC 7911)
834    if let Some(path_id) = prefix.path_id {
835        output.extend_from_slice(&path_id.to_be_bytes());
836    }
837
838    // 2. Calculate total length in bits
839    // Per RFC 8277 §2.4: 24 bits for compatibility field + prefix bits
840    let prefix_bits = prefix.prefix.prefix_len() as usize;
841    let total_bits =
842        24usize
843            .checked_add(prefix_bits)
844            .ok_or(LabeledNlriEncodeError::TotalBitsOverflow {
845                total_bits: prefix_bits.saturating_add(24),
846                max: 255,
847            })?;
848
849    // Validate: total_bits must fit in u8 (0-255) per RFC 4760
850    if total_bits > 255 {
851        return Err(LabeledNlriEncodeError::TotalBitsOverflow {
852            total_bits,
853            max: 255,
854        });
855    }
856
857    output.push(total_bits as u8);
858
859    // 3. Write RFC 8277 compatibility field (3 opaque bytes)
860    // Per RFC 8277 §2.4: SHOULD be 0x800000 on transmission
861    output.extend_from_slice(&[0x80, 0x00, 0x00]);
862
863    // 4. Write prefix bytes (truncated to prefix_bits)
864    let prefix_bytes = prefix_bits.div_ceil(8);
865    let prefix_octets = match prefix.prefix {
866        IpNet::V4(p) => p.addr().octets().to_vec(),
867        IpNet::V6(p) => p.addr().octets().to_vec(),
868    };
869    output.extend_from_slice(&prefix_octets[..prefix_bytes]);
870
871    Ok(output)
872}
873
874#[cfg(test)]
875mod tests {
876    use super::*;
877    use std::str::FromStr;
878
879    #[test]
880    fn test_mpls_label_new() {
881        let label = MplsLabel::try_new(100).unwrap();
882        assert_eq!(label.value(), 100);
883        assert!(!label.is_reserved());
884    }
885
886    #[test]
887    fn test_mpls_label_too_large() {
888        let result = MplsLabel::try_new(0x0010_0000); // 21st bit set
889        assert!(result.is_err());
890    }
891
892    #[test]
893    fn test_mpls_label_reserved() {
894        let label = MplsLabel::try_new(0).unwrap();
895        assert!(label.is_reserved());
896        assert!(label.is_ipv4_explicit_null());
897
898        let label = MplsLabel::try_new(2).unwrap();
899        assert!(label.is_reserved());
900        assert!(label.is_ipv6_explicit_null());
901
902        let label = MplsLabel::try_new(3).unwrap();
903        assert!(label.is_reserved());
904        assert!(label.is_implicit_null());
905
906        let label = MplsLabel::try_new(15).unwrap();
907        assert!(label.is_reserved());
908
909        let label = MplsLabel::try_new(16).unwrap();
910        assert!(!label.is_reserved());
911    }
912
913    #[test]
914    fn test_mpls_label_encode_decode() {
915        let label = MplsLabel::try_new(24001).unwrap();
916
917        // Encode with BoS=1
918        let encoded = label.encode(true);
919        assert_eq!(encoded, [0x05, 0xDC, 0x11]); // 0x5DC1 << 4 | 1
920
921        // Decode
922        let (decoded, bos) = MplsLabel::decode(encoded);
923        assert_eq!(decoded.value(), 24001);
924        assert!(bos);
925
926        // Encode with BoS=0
927        let encoded = label.encode(false);
928        assert_eq!(encoded, [0x05, 0xDC, 0x10]); // 0x5DC1 << 4 | 0
929
930        // Decode
931        let (decoded, bos) = MplsLabel::decode(encoded);
932        assert_eq!(decoded.value(), 24001);
933        assert!(!bos);
934    }
935
936    #[test]
937    fn test_labeled_network_prefix_new() {
938        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
939        let labels = SmallVec::from_vec(vec![MplsLabel::try_new(100).unwrap()]);
940
941        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, None).unwrap();
942        assert_eq!(labeled.label_count(), 1);
943        assert!(!labeled.has_multiple_labels());
944    }
945
946    #[test]
947    fn test_labeled_network_prefix_empty_labels() {
948        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
949        let labels: SmallVec<[MplsLabel; 2]> = SmallVec::new();
950
951        let result = LabeledNetworkPrefix::try_new(prefix, labels, None);
952        assert!(matches!(
953            result,
954            Err(LabeledNetworkPrefixError::EmptyLabelStack)
955        ));
956    }
957
958    #[test]
959    fn test_labeled_nlri_config_validation() {
960        // Valid config
961        let config = LabeledNlriConfig::try_new(false, LabeledNlriMode::SingleLabel, 16, None);
962        assert!(config.is_ok());
963
964        // Invalid max_labels (0)
965        let result = LabeledNlriConfig::try_new(false, LabeledNlriMode::SingleLabel, 0, None);
966        assert!(matches!(
967            result,
968            Err(LabeledNlriConfigError::InvalidMaxLabels(0))
969        ));
970
971        // Invalid max_labels (255)
972        let result = LabeledNlriConfig::try_new(false, LabeledNlriMode::SingleLabel, 255, None);
973        assert!(matches!(
974            result,
975            Err(LabeledNlriConfigError::InvalidMaxLabels(255))
976        ));
977
978        // Invalid peer_max_labels (0) - only 0 is rejected per RFC 8277 §2.1
979        let result = LabeledNlriConfig::try_new(false, LabeledNlriMode::MultiLabel, 16, Some(0));
980        assert!(matches!(
981            result,
982            Err(LabeledNlriConfigError::InvalidPeerMaxLabels(0))
983        ));
984
985        // Valid peer_max_labels (1) - RFC 8277 §2.1 allows this
986        let result = LabeledNlriConfig::try_new(false, LabeledNlriMode::MultiLabel, 16, Some(1));
987        assert!(result.is_ok());
988    }
989
990    #[test]
991    fn test_encode_labeled_prefix_single_label_mode() {
992        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
993        let labels = SmallVec::from_vec(vec![MplsLabel::try_new(24001).unwrap()]);
994        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, None).unwrap();
995
996        // SingleLabel mode should succeed
997        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::SingleLabel, false, None);
998        assert!(result.is_ok());
999
1000        // Should be: total_bits (48 = 0x30), label (0x05DC11), prefix (0xC00002)
1001        let encoded = result.unwrap();
1002        assert_eq!(encoded, vec![0x30, 0x05, 0xDC, 0x11, 0xC0, 0x00, 0x02]);
1003    }
1004
1005    #[test]
1006    fn test_encode_labeled_prefix_single_label_mode_multiple_labels() {
1007        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
1008        let labels = SmallVec::from_vec(vec![
1009            MplsLabel::try_new(24001).unwrap(),
1010            MplsLabel::try_new(24002).unwrap(),
1011        ]);
1012        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, None).unwrap();
1013
1014        // SingleLabel mode should fail with multiple labels
1015        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::SingleLabel, false, None);
1016        assert!(matches!(
1017            result,
1018            Err(LabeledNlriEncodeError::SingleLabelModeWithMultipleLabels { label_count: 2 })
1019        ));
1020    }
1021
1022    #[test]
1023    fn test_encode_labeled_prefix_multi_label_mode() {
1024        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
1025        let labels = SmallVec::from_vec(vec![
1026            MplsLabel::try_new(24001).unwrap(),
1027            MplsLabel::try_new(24002).unwrap(),
1028        ]);
1029        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, None).unwrap();
1030
1031        // MultiLabel mode should succeed
1032        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::MultiLabel, false, None);
1033        assert!(result.is_ok());
1034
1035        // Should be: total_bits (72 = 0x48),
1036        // label1 (0x05DC10 - BoS=0), label2 (0x05DC21 - BoS=1),
1037        // prefix (0xC00002)
1038        let encoded = result.unwrap();
1039        assert_eq!(
1040            encoded,
1041            vec![0x48, 0x05, 0xDC, 0x10, 0x05, 0xDC, 0x21, 0xC0, 0x00, 0x02]
1042        );
1043    }
1044
1045    #[test]
1046    fn test_encode_labeled_prefix_multi_label_mode_with_path_id() {
1047        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
1048        let labels = SmallVec::from_vec(vec![
1049            MplsLabel::try_new(24001).unwrap(),
1050            MplsLabel::try_new(24002).unwrap(),
1051        ]);
1052        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, Some(123)).unwrap();
1053
1054        // MultiLabel mode with ADD-PATH enabled should succeed
1055        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::MultiLabel, true, None);
1056        assert!(result.is_ok());
1057
1058        // Should be: path_id (0x0000007B), total_bits (72 = 0x48),
1059        // label1 (0x05DC10 - BoS=0), label2 (0x05DC21 - BoS=1),
1060        // prefix (0xC00002)
1061        let encoded = result.unwrap();
1062        assert_eq!(
1063            encoded,
1064            vec![
1065                0x00, 0x00, 0x00, 0x7B, 0x48, 0x05, 0xDC, 0x10, 0x05, 0xDC, 0x21, 0xC0, 0x00, 0x02
1066            ]
1067        );
1068    }
1069
1070    #[test]
1071    fn test_encode_labeled_withdrawal() {
1072        let prefix = NetworkPrefix::new(IpNet::from_str("192.0.2.0/24").unwrap(), None);
1073
1074        let result = encode_labeled_withdrawal(&prefix);
1075        assert!(result.is_ok());
1076
1077        // Should be: total_bits (48 = 0x30), compatibility field (0x800000), prefix (0xC00002)
1078        let encoded = result.unwrap();
1079        assert_eq!(encoded, vec![0x30, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x02]);
1080    }
1081
1082    #[test]
1083    fn test_encode_labeled_withdrawal_with_path_id() {
1084        let prefix = NetworkPrefix::new(IpNet::from_str("192.0.2.0/24").unwrap(), Some(123));
1085
1086        let result = encode_labeled_withdrawal(&prefix);
1087        assert!(result.is_ok());
1088
1089        // Should be: path_id (0x0000007B), total_bits (48 = 0x30),
1090        // compatibility field (0x800000), prefix (0xC00002)
1091        let encoded = result.unwrap();
1092        assert_eq!(
1093            encoded,
1094            vec![0x00, 0x00, 0x00, 0x7B, 0x30, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x02]
1095        );
1096    }
1097
1098    #[test]
1099    fn test_encode_labeled_prefix_add_path_not_negotiated() {
1100        let prefix = IpNet::from_str("192.0.2.0/24").unwrap();
1101        let labels = SmallVec::from_vec(vec![MplsLabel::try_new(100).unwrap()]);
1102        let labeled = LabeledNetworkPrefix::try_new(prefix, labels, Some(123)).unwrap();
1103
1104        // Trying to encode with path_id but add_path=false should fail
1105        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::SingleLabel, false, None);
1106        assert!(matches!(
1107            result,
1108            Err(LabeledNlriEncodeError::AddPathNotNegotiated)
1109        ));
1110
1111        // With add_path=true it should succeed
1112        let result = encode_labeled_prefix(&labeled, LabeledNlriMode::SingleLabel, true, None);
1113        assert!(result.is_ok());
1114    }
1115}