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