Skip to main content

bgpkit_parser/parser/bgp/attributes/
mod.rs

1mod attr_01_origin;
2mod attr_02_17_as_path;
3mod attr_03_next_hop;
4mod attr_04_med;
5mod attr_05_local_pref;
6mod attr_07_18_aggregator;
7mod attr_08_communities;
8mod attr_09_originator;
9mod attr_10_13_cluster;
10mod attr_14_15_nlri;
11mod attr_16_25_extended_communities;
12mod attr_23_tunnel_encap;
13mod attr_26_aigp;
14mod attr_29_linkstate;
15mod attr_32_large_communities;
16mod attr_35_otc;
17mod attr_37_sfp;
18mod attr_38_bfd_discriminator;
19mod attr_40_bgp_prefix_sid;
20mod attr_41_bier;
21
22use bytes::{Buf, BufMut, Bytes, BytesMut};
23use log::{debug, warn};
24use std::net::IpAddr;
25
26use crate::models::*;
27
28use crate::error::{BgpValidationWarning, ParserError};
29use crate::parser::bgp::attributes::attr_01_origin::{encode_origin, parse_origin};
30use crate::parser::bgp::attributes::attr_02_17_as_path::encode_as_path;
31pub(crate) use crate::parser::bgp::attributes::attr_02_17_as_path::parse_as_path;
32use crate::parser::bgp::attributes::attr_03_next_hop::{encode_next_hop, parse_next_hop};
33use crate::parser::bgp::attributes::attr_04_med::{encode_med, parse_med};
34use crate::parser::bgp::attributes::attr_05_local_pref::{encode_local_pref, parse_local_pref};
35use crate::parser::bgp::attributes::attr_07_18_aggregator::{encode_aggregator, parse_aggregator};
36use crate::parser::bgp::attributes::attr_08_communities::{
37    encode_regular_communities, parse_regular_communities,
38};
39use crate::parser::bgp::attributes::attr_09_originator::{
40    encode_originator_id, parse_originator_id,
41};
42use crate::parser::bgp::attributes::attr_10_13_cluster::{encode_clusters, parse_clusters};
43use crate::parser::bgp::attributes::attr_14_15_nlri::encode_nlri;
44pub(crate) use crate::parser::bgp::attributes::attr_14_15_nlri::parse_nlri;
45use crate::parser::bgp::attributes::attr_16_25_extended_communities::{
46    encode_extended_communities, encode_ipv6_extended_communities, parse_extended_community,
47    parse_ipv6_extended_community,
48};
49use crate::parser::bgp::attributes::attr_23_tunnel_encap::{
50    encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute,
51};
52use crate::parser::bgp::attributes::attr_26_aigp::{encode_aigp, parse_aigp};
53use crate::parser::bgp::attributes::attr_29_linkstate::{
54    encode_link_state_attribute, parse_link_state_attribute,
55};
56use crate::parser::bgp::attributes::attr_32_large_communities::{
57    encode_large_communities, parse_large_communities,
58};
59use crate::parser::bgp::attributes::attr_35_otc::{
60    encode_only_to_customer, parse_only_to_customer,
61};
62use crate::parser::bgp::attributes::attr_37_sfp::{encode_sfp, parse_sfp};
63use crate::parser::bgp::attributes::attr_38_bfd_discriminator::{
64    encode_bfd_discriminator, parse_bfd_discriminator,
65};
66use crate::parser::bgp::attributes::attr_40_bgp_prefix_sid::{
67    encode_bgp_prefix_sid, parse_bgp_prefix_sid,
68};
69use crate::parser::bgp::attributes::attr_41_bier::{encode_bier, parse_bier};
70use crate::parser::ReadUtils;
71
72/// Validate attribute flags according to RFC 4271 and RFC 7606
73fn validate_attribute_flags(
74    attr_type: AttrType,
75    flags: AttrFlags,
76    warnings: &mut Vec<BgpValidationWarning>,
77) {
78    let expected_flags = match attr_type {
79        // Well-known mandatory attributes
80        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP => AttrFlags::TRANSITIVE,
81        // Well-known discretionary attributes
82        AttrType::ATOMIC_AGGREGATE => AttrFlags::TRANSITIVE,
83        // Optional non-transitive attributes
84        AttrType::MULTI_EXIT_DISCRIMINATOR
85        | AttrType::ORIGINATOR_ID
86        | AttrType::CLUSTER_LIST
87        | AttrType::MP_REACHABLE_NLRI
88        | AttrType::MP_UNREACHABLE_NLRI => AttrFlags::OPTIONAL,
89        // Optional transitive attributes
90        AttrType::AGGREGATOR
91        | AttrType::AS4_AGGREGATOR
92        | AttrType::AS4_PATH
93        | AttrType::COMMUNITIES
94        | AttrType::EXTENDED_COMMUNITIES
95        | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
96        | AttrType::LARGE_COMMUNITIES
97        | AttrType::ONLY_TO_CUSTOMER => AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE,
98        // LOCAL_PREFERENCE is well-known mandatory for IBGP
99        AttrType::LOCAL_PREFERENCE => AttrFlags::TRANSITIVE,
100        // Unknown or development attributes
101        _ => return, // Don't validate unknown attributes
102    };
103
104    // Check if flags match expected (ignoring EXTENDED and PARTIAL flags for this check)
105    let relevant_flags = flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE);
106    if relevant_flags != expected_flags {
107        warnings.push(BgpValidationWarning::AttributeFlagsError {
108            attr_type,
109            expected_flags: expected_flags.bits(),
110            actual_flags: relevant_flags.bits(),
111        });
112    }
113
114    // Check partial flag constraint
115    if flags.contains(AttrFlags::PARTIAL) {
116        match attr_type {
117            // Partial bit MUST be 0 for well-known attributes and optional non-transitive
118            AttrType::ORIGIN
119            | AttrType::AS_PATH
120            | AttrType::NEXT_HOP
121            | AttrType::LOCAL_PREFERENCE
122            | AttrType::ATOMIC_AGGREGATE
123            | AttrType::MULTI_EXIT_DISCRIMINATOR
124            | AttrType::ORIGINATOR_ID
125            | AttrType::CLUSTER_LIST
126            | AttrType::MP_REACHABLE_NLRI
127            | AttrType::MP_UNREACHABLE_NLRI => {
128                warnings.push(BgpValidationWarning::AttributeFlagsError {
129                    attr_type,
130                    expected_flags: expected_flags.bits(),
131                    actual_flags: flags.bits(),
132                });
133            }
134            _ => {} // Partial is OK for optional transitive attributes
135        }
136    }
137}
138
139fn is_raw_retained_attr(attr_type: AttrType) -> bool {
140    matches!(
141        attr_type,
142        AttrType::RESERVED
143            | AttrType::PMSI_TUNNEL
144            | AttrType::TRAFFIC_ENGINEERING
145            | AttrType::PE_DISTINGUISHER_LABELS
146            | AttrType::BGPSEC_PATH
147            | AttrType::ATTR_SET
148    )
149}
150
151/// Check if an attribute type is well-known mandatory
152fn is_well_known_mandatory(attr_type: AttrType) -> bool {
153    matches!(
154        attr_type,
155        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP | AttrType::LOCAL_PREFERENCE
156    )
157}
158
159pub(crate) struct AttributeValidationState {
160    warnings: Vec<BgpValidationWarning>,
161    attr_mask: [u64; 4],
162}
163
164impl AttributeValidationState {
165    pub(crate) fn new() -> Self {
166        Self {
167            warnings: Vec::new(),
168            attr_mask: [0; 4],
169        }
170    }
171
172    fn has_raw_attr(&self, attr: u8) -> bool {
173        (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
174    }
175
176    pub(crate) fn has_attr(&self, attr_type: AttrType) -> bool {
177        self.has_raw_attr(u8::from(attr_type))
178    }
179
180    fn set_attr(&mut self, attr: u8) {
181        self.attr_mask[(attr / 64) as usize] |= 1u64 << (attr % 64);
182    }
183
184    pub(crate) fn observe_header(
185        &mut self,
186        raw_attr_type: u8,
187        attr_type: AttrType,
188        flags: AttrFlags,
189        length: usize,
190    ) -> bool {
191        if self.has_raw_attr(raw_attr_type) {
192            self.warnings
193                .push(BgpValidationWarning::DuplicateAttribute { attr_type });
194        }
195        self.set_attr(raw_attr_type);
196
197        validate_attribute_flags(attr_type, flags, &mut self.warnings);
198        validate_attribute_length(attr_type, length, &mut self.warnings);
199
200        flags.contains(AttrFlags::PARTIAL)
201    }
202
203    pub(crate) fn observe_parse_error(
204        &mut self,
205        attr_type: AttrType,
206        partial: bool,
207        error: &ParserError,
208    ) {
209        if partial {
210            self.warnings
211                .push(BgpValidationWarning::PartialAttributeError {
212                    attr_type,
213                    reason: error.to_string(),
214                });
215            debug!("PARTIAL attribute error: {}", error);
216        } else if is_well_known_mandatory(attr_type) {
217            self.warnings
218                .push(BgpValidationWarning::MalformedAttributeList {
219                    reason: format!(
220                        "Well-known mandatory attribute {} parsing failed: {}",
221                        u8::from(attr_type),
222                        error
223                    ),
224                });
225            debug!(
226                "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
227                error
228            );
229        } else {
230            self.warnings
231                .push(BgpValidationWarning::OptionalAttributeError {
232                    attr_type,
233                    reason: error.to_string(),
234                });
235            debug!("Optional attribute error, discarding: {}", error);
236        }
237    }
238
239    pub(crate) fn check_mandatory_attributes(
240        &mut self,
241        is_announcement: bool,
242        has_standard_nlri: bool,
243    ) {
244        if !is_announcement {
245            return;
246        }
247
248        if !self.has_attr(AttrType::ORIGIN) {
249            self.warnings
250                .push(BgpValidationWarning::MissingWellKnownAttribute {
251                    attr_type: AttrType::ORIGIN,
252                });
253        }
254
255        if !self.has_attr(AttrType::AS_PATH) {
256            self.warnings
257                .push(BgpValidationWarning::MissingWellKnownAttribute {
258                    attr_type: AttrType::AS_PATH,
259                });
260        }
261
262        let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
263        if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
264            self.warnings
265                .push(BgpValidationWarning::MissingWellKnownAttribute {
266                    attr_type: AttrType::NEXT_HOP,
267                });
268        }
269    }
270
271    pub(crate) fn finish(self) -> (Vec<BgpValidationWarning>, [u64; 4]) {
272        (self.warnings, self.attr_mask)
273    }
274}
275
276/// Validate attribute length constraints
277fn validate_attribute_length(
278    attr_type: AttrType,
279    length: usize,
280    warnings: &mut Vec<BgpValidationWarning>,
281) {
282    let expected_length = match attr_type {
283        AttrType::ORIGIN => Some(1),
284        AttrType::NEXT_HOP => Some(4), // IPv4 next hop
285        AttrType::MULTI_EXIT_DISCRIMINATOR => Some(4),
286        AttrType::LOCAL_PREFERENCE => Some(4),
287        AttrType::ATOMIC_AGGREGATE => Some(0),
288        AttrType::ORIGINATOR_ID => Some(4),
289        AttrType::ONLY_TO_CUSTOMER => Some(4),
290        // Variable length attributes - no fixed constraint
291        AttrType::AS_PATH
292        | AttrType::AS4_PATH
293        | AttrType::AGGREGATOR
294        | AttrType::AS4_AGGREGATOR
295        | AttrType::COMMUNITIES
296        | AttrType::EXTENDED_COMMUNITIES
297        | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
298        | AttrType::LARGE_COMMUNITIES
299        | AttrType::CLUSTER_LIST
300        | AttrType::MP_REACHABLE_NLRI
301        | AttrType::MP_UNREACHABLE_NLRI => None,
302        _ => None, // Unknown attributes
303    };
304
305    if let Some(expected) = expected_length {
306        if length != expected {
307            warnings.push(BgpValidationWarning::AttributeLengthError {
308                attr_type,
309                expected_length: Some(expected),
310                actual_length: length,
311            });
312        }
313    }
314}
315
316/// Parse BGP attributes given a slice of u8 and some options.
317///
318/// The `data: &[u8]` contains the entirety of the attributes bytes, therefore the size of
319/// the slice is the total byte length of the attributes section of the message.
320pub fn parse_attributes(
321    mut data: Bytes,
322    asn_len: &AsnLength,
323    add_path: bool,
324    afi: Option<Afi>,
325    safi: Option<Safi>,
326    prefixes: Option<&[NetworkPrefix]>,
327) -> Result<Attributes, ParserError> {
328    // Estimate capacity from data size: each attribute is at least 3 bytes
329    // (flag + type + length). Cap at 256 to avoid over-allocation for corrupted data.
330    let estimated_attrs = (data.remaining() / 3).min(256);
331    let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
332    let mut validation = AttributeValidationState::new();
333
334    while data.remaining() >= 3 {
335        // each attribute is at least 3 bytes: flag(1) + type(1) + length(1)
336        // thus the while loop condition is set to be at least 3 bytes to read.
337
338        // has content to read
339        let flag = AttrFlags::from_bits_retain(data.read_u8()?);
340        let attr_type = data.read_u8()?;
341        let attr_length = match flag.contains(AttrFlags::EXTENDED) {
342            false => data.read_u8()? as usize,
343            true => data.read_u16()? as usize,
344        };
345
346        debug!(
347            "reading attribute: type -- {:?}, length -- {}",
348            &attr_type, attr_length
349        );
350
351        let attr_type = AttrType::from(attr_type);
352
353        let partial = validation.observe_header(attr_type.into(), attr_type, flag, attr_length);
354
355        let bytes_left = data.remaining();
356
357        if data.remaining() < attr_length {
358            warn!(
359                "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
360                attr_type, attr_length, bytes_left
361            );
362            // break and return already parsed attributes
363            break;
364        }
365
366        // we know data has enough bytes to read, so we can split the bytes into a new Bytes object
367        data.has_n_remaining(attr_length)?;
368        let mut attr_data = data.split_to(attr_length);
369        let raw_bytes = attr_data.clone();
370        let raw_code = u8::from(attr_type);
371
372        if let Some(t) = get_deprecated_attr_type(raw_code) {
373            debug!("deprecated attribute type: {} - {}", raw_code, t);
374            attributes.push(Attribute {
375                value: AttributeValue::Deprecated(AttrRaw {
376                    code: raw_code,
377                    bytes: raw_bytes,
378                }),
379                flag,
380            });
381            continue;
382        }
383
384        if matches!(attr_type, AttrType::Unknown(_)) {
385            debug!("unknown attribute type: {}", raw_code);
386            attributes.push(Attribute {
387                value: AttributeValue::Unknown(AttrRaw {
388                    code: raw_code,
389                    bytes: raw_bytes,
390                }),
391                flag,
392            });
393            continue;
394        }
395
396        if is_raw_retained_attr(attr_type) {
397            debug!("raw-retained attribute type: {}", raw_code);
398            attributes.push(Attribute {
399                value: AttributeValue::Raw(AttrRaw {
400                    code: raw_code,
401                    bytes: raw_bytes,
402                }),
403                flag,
404            });
405            continue;
406        }
407
408        let attr = match attr_type {
409            AttrType::ORIGIN => parse_origin(attr_data),
410            AttrType::AS_PATH => {
411                parse_as_path(attr_data, asn_len).map(|path| AttributeValue::AsPath {
412                    path,
413                    is_as4: false,
414                })
415            }
416            AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
417            AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
418            AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
419            AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
420            AttrType::AGGREGATOR => {
421                parse_aggregator(attr_data, asn_len).map(|(asn, id)| AttributeValue::Aggregator {
422                    asn,
423                    id,
424                    is_as4: false,
425                })
426            }
427            AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
428            AttrType::CLUSTER_LIST => parse_clusters(attr_data),
429            AttrType::MP_REACHABLE_NLRI => {
430                parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
431            }
432            AttrType::MP_UNREACHABLE_NLRI => {
433                parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
434            }
435            AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32)
436                .map(|path| AttributeValue::AsPath { path, is_as4: true }),
437            AttrType::AS4_AGGREGATOR => {
438                parse_aggregator(attr_data, &AsnLength::Bits32).map(|(asn, id)| {
439                    AttributeValue::Aggregator {
440                        asn,
441                        id,
442                        is_as4: true,
443                    }
444                })
445            }
446
447            // communities
448            AttrType::COMMUNITIES => parse_regular_communities(attr_data),
449            AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
450            AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
451            AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
452                parse_ipv6_extended_community(attr_data)
453            }
454            AttrType::DEVELOPMENT => {
455                let mut value = vec![];
456                for _i in 0..attr_length {
457                    value.push(attr_data.read_u8()?);
458                }
459                Ok(AttributeValue::Development(value))
460            }
461            AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
462            AttrType::AIGP => parse_aigp(attr_data),
463            AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
464            AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
465            AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data),
466            AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data),
467            AttrType::BGP_PREFIX_SID => parse_bgp_prefix_sid(attr_data),
468            AttrType::BIER => parse_bier(attr_data),
469            _ => Err(ParserError::Unsupported(format!(
470                "unsupported attribute type: {attr_type:?}"
471            ))),
472        };
473
474        match attr {
475            Ok(value) => {
476                assert_eq!(attr_type, value.attr_type());
477                attributes.push(Attribute { value, flag });
478            }
479            Err(e) => {
480                validation.observe_parse_error(attr_type, partial, &e);
481                attributes.push(Attribute {
482                    value: AttributeValue::Raw(AttrRaw {
483                        code: raw_code,
484                        bytes: raw_bytes,
485                    }),
486                    flag,
487                });
488                continue;
489            }
490        };
491    }
492
493    let (validation_warnings, attr_mask) = validation.finish();
494    Ok(Attributes {
495        inner: attributes,
496        validation_warnings,
497        attr_mask,
498    })
499}
500
501impl Attribute {
502    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
503        let mut bytes = BytesMut::new();
504
505        let flag = self.flag.bits();
506        let type_code = self.value.attr_code();
507
508        bytes.put_u8(flag);
509        bytes.put_u8(type_code);
510
511        let value_bytes = match &self.value {
512            AttributeValue::Origin(v) => encode_origin(v),
513            AttributeValue::AsPath { path, is_as4 } => {
514                let four_byte = match is_as4 {
515                    true => AsnLength::Bits32,
516                    false => match asn_len.is_four_byte() {
517                        true => AsnLength::Bits32,
518                        false => AsnLength::Bits16,
519                    },
520                };
521                encode_as_path(path, four_byte)
522            }
523            AttributeValue::NextHop(v) => encode_next_hop(v),
524            AttributeValue::MultiExitDiscriminator(v) => encode_med(*v),
525            AttributeValue::LocalPreference(v) => encode_local_pref(*v),
526            AttributeValue::OnlyToCustomer(v) => encode_only_to_customer(v.into()),
527            AttributeValue::AtomicAggregate => Bytes::default(),
528            AttributeValue::Aggregator { asn, id, is_as4: _ } => {
529                encode_aggregator(asn, &IpAddr::from(*id))
530            }
531            AttributeValue::Communities(v) => encode_regular_communities(v),
532            AttributeValue::ExtendedCommunities(v) => encode_extended_communities(v),
533            AttributeValue::LargeCommunities(v) => encode_large_communities(v),
534            AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
535                encode_ipv6_extended_communities(v)
536            }
537            AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
538            AttributeValue::Clusters(v) => encode_clusters(v),
539            AttributeValue::MpReachNlri(v) => {
540                // Infer ADD-PATH from presence of path_id in any labeled prefix
541                let add_path = v
542                    .labeled_prefixes
543                    .as_ref()
544                    .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
545                encode_nlri(v, true, add_path).unwrap_or_else(|e| {
546                    log::warn!("Failed to encode MP_REACH_NLRI: {}", e);
547                    Bytes::new()
548                })
549            }
550            AttributeValue::MpUnreachNlri(v) => {
551                // Withdrawals don't use ADD-PATH encoding per RFC 8277
552                encode_nlri(v, false, false).unwrap_or_else(|e| {
553                    log::warn!("Failed to encode MP_UNREACH_NLRI: {}", e);
554                    Bytes::new()
555                })
556            }
557            AttributeValue::LinkState(v) => encode_link_state_attribute(v),
558            AttributeValue::TunnelEncapsulation(v) => encode_tunnel_encapsulation_attribute(v),
559            AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v),
560            AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v),
561            AttributeValue::Bier(v) => encode_bier(v),
562            AttributeValue::Sfp(v) => encode_sfp(v),
563            AttributeValue::Development(v) => Bytes::copy_from_slice(v),
564            AttributeValue::Raw(v) => v.bytes.clone(),
565            AttributeValue::Deprecated(v) => v.bytes.clone(),
566            AttributeValue::Unknown(v) => v.bytes.clone(),
567            AttributeValue::Aigp(v) => encode_aigp(v),
568            AttributeValue::AttrSet(_v) => {
569                // ATTR_SET encoding not yet implemented - return empty bytes
570                Bytes::new()
571            }
572        };
573
574        match self.is_extended() {
575            false => {
576                bytes.put_u8(value_bytes.len() as u8);
577            }
578            true => {
579                bytes.put_u16(value_bytes.len() as u16);
580            }
581        }
582        bytes.extend(value_bytes);
583        bytes.freeze()
584    }
585}
586
587impl Attributes {
588    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
589        let mut bytes = BytesMut::new();
590        for attr in &self.inner {
591            bytes.extend(attr.encode(asn_len));
592        }
593        bytes.freeze()
594    }
595}
596
597#[cfg(test)]
598mod tests {
599    use super::*;
600
601    #[test]
602    fn test_unknwon_attribute_type() {
603        let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
604        let asn_len = AsnLength::Bits16;
605        let add_path = false;
606        let afi = None;
607        let safi = None;
608        let prefixes = None;
609        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
610        assert!(attributes.is_ok());
611        let attributes = attributes.unwrap();
612        assert_eq!(attributes.inner.len(), 1);
613        assert_eq!(
614            attributes.inner[0].value.attr_type(),
615            AttrType::Unknown(254)
616        );
617    }
618
619    #[test]
620    fn test_rfc7606_attribute_flags_error() {
621        // Create an ORIGIN attribute with wrong flags (should be transitive, not optional)
622        let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); // Optional flag set incorrectly
623        let asn_len = AsnLength::Bits16;
624        let add_path = false;
625        let afi = None;
626        let safi = None;
627        let prefixes = None;
628
629        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
630
631        // Should have validation warning for incorrect flags
632        assert!(attributes.has_validation_warnings());
633        let warnings = attributes.validation_warnings();
634        // Will have attribute flags error + missing mandatory attributes
635        assert!(!warnings.is_empty());
636
637        match &warnings[0] {
638            BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
639                assert_eq!(*attr_type, AttrType::ORIGIN);
640            }
641            _ => panic!("Expected AttributeFlagsError warning"),
642        }
643    }
644
645    #[test]
646    fn test_rfc7606_missing_mandatory_attribute() {
647        // Attributes with only LOCAL_PREF (missing ORIGIN, AS_PATH, NEXT_HOP)
648        let data = Bytes::from(vec![
649            0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, // LOCAL_PREF = 100
650        ]);
651        let asn_len = AsnLength::Bits16;
652        let add_path = false;
653        let afi = None;
654        let safi = None;
655        let prefixes = None;
656
657        let mut attributes =
658            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
659        // Manually trigger mandatory check as an announcement with standard NLRI
660        attributes.check_mandatory_attributes(true, true);
661
662        // Should have warnings for missing mandatory attributes
663        assert!(attributes.has_validation_warnings());
664        let warnings = attributes.validation_warnings();
665        // LOCAL_PREF is not a withdrawal, so ORIGIN, AS_PATH, NEXT_HOP are required
666        assert_eq!(warnings.len(), 3); // ORIGIN, AS_PATH, NEXT_HOP
667
668        for warning in warnings {
669            match warning {
670                BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
671                    assert!(matches!(
672                        attr_type,
673                        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
674                    ));
675                }
676                _ => panic!("Expected MissingWellKnownAttribute warning"),
677            }
678        }
679    }
680
681    #[test]
682    fn test_mp_reach_no_next_hop() {
683        // Attributes with MP_REACH_NLRI (missing ORIGIN, AS_PATH)
684        // MP_REACH_NLRI is type 14 (0x0E).
685        // We just need a dummy MP_REACH_NLRI.
686        let data = Bytes::from(vec![
687            0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, // AFI=1, SAFI=1
688            0x00, // Next Hop Len = 0 (invalid for parsing, but enough to trigger logic)
689            0x00, // Reserved
690            0x00, // NLRI
691        ]);
692        let asn_len = AsnLength::Bits16;
693        let add_path = false;
694        let afi = None;
695        let safi = None;
696        let prefixes = None;
697
698        let mut attributes =
699            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
700        // Manually trigger mandatory check as an announcement but NO standard NLRI (MP only)
701        attributes.check_mandatory_attributes(true, false);
702
703        // Should NOT have NEXT_HOP warning because MP_REACH_NLRI is present and has_standard_nlri is false
704        let warnings = attributes.validation_warnings();
705        let has_next_hop_warning = warnings.iter().any(|w| {
706            matches!(
707                w,
708                BgpValidationWarning::MissingWellKnownAttribute {
709                    attr_type: AttrType::NEXT_HOP
710                }
711            )
712        });
713        assert!(!has_next_hop_warning);
714    }
715
716    #[test]
717    fn test_pure_withdrawal_no_warnings() {
718        // Empty attributes - pure withdrawal
719        let data = Bytes::from(vec![]);
720        let asn_len = AsnLength::Bits16;
721        let add_path = false;
722        let afi = None;
723        let safi = None;
724        let prefixes = None;
725
726        let mut attributes =
727            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
728        // Manually trigger mandatory check as a withdrawal
729        attributes.check_mandatory_attributes(false, false);
730
731        // Should have NO warnings
732        assert!(!attributes.has_validation_warnings());
733
734        // Attributes with only MP_UNREACH_NLRI - pure withdrawal
735        let data = Bytes::from(vec![
736            0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, // AFI=1, SAFI=1
737        ]);
738        let mut attributes =
739            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
740        attributes.check_mandatory_attributes(false, false);
741        assert!(!attributes.has_validation_warnings());
742    }
743
744    #[test]
745    fn test_rfc7606_duplicate_attribute() {
746        // Create two ORIGIN attributes
747        let data = Bytes::from(vec![
748            0x40, 0x01, 0x01, 0x00, // First ORIGIN attribute
749            0x40, 0x01, 0x01, 0x01, // Second ORIGIN attribute (duplicate)
750        ]);
751        let asn_len = AsnLength::Bits16;
752        let add_path = false;
753        let afi = None;
754        let safi = None;
755        let prefixes = None;
756
757        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
758
759        // Should have warning for duplicate attribute
760        assert!(attributes.has_validation_warnings());
761        let warnings = attributes.validation_warnings();
762
763        // Should have at least one duplicate attribute warning
764        let has_duplicate_warning = warnings
765            .iter()
766            .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
767        assert!(has_duplicate_warning);
768    }
769
770    #[test]
771    fn test_attribute_type_boundaries() {
772        let asn_len = AsnLength::Bits16;
773        let add_path = false;
774        let afi = None;
775        let safi = None;
776        let prefixes = None;
777
778        // Required attributes for valid BGP message
779        const REQUIRED_ATTRS: &[u8] = &[
780            0x40, 0x01, 0x01, 0x00, // origin
781            0x40, 0x02, 0x00, // as_path
782            0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, // next_hop
783        ];
784
785        // Test highest (development) attribute type
786        let mut data = REQUIRED_ATTRS.to_vec();
787        data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); // development
788        let data = Bytes::from(data);
789
790        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
791
792        assert!(attributes.has_attr(AttrType::DEVELOPMENT));
793        assert!(!attributes.has_validation_warnings());
794
795        // Test lowest (reserved) attribute type
796        let mut data = REQUIRED_ATTRS.to_vec();
797        data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); // reserved
798        let data = Bytes::from(data);
799
800        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
801
802        assert!(attributes.inner.iter().any(|attr| {
803            matches!(
804                &attr.value,
805                AttributeValue::Raw(raw)
806                    if raw.code == u8::from(AttrType::RESERVED)
807                        && raw.bytes == Bytes::from_static(&[0x01])
808            )
809        }));
810        assert!(!attributes.validation_warnings.iter().any(|vw| {
811            matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
812        }));
813    }
814
815    #[test]
816    fn test_raw_retention_for_known_unsupported_attribute() {
817        let data = Bytes::from(vec![0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc]); // PMSI_TUNNEL
818        let attributes =
819            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
820
821        assert_eq!(attributes.inner.len(), 1);
822        match &attributes.inner[0].value {
823            AttributeValue::Raw(raw) => {
824                assert_eq!(raw.code, 22);
825                assert_eq!(raw.attr_type(), AttrType::PMSI_TUNNEL);
826                assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb, 0xcc]));
827            }
828            value => panic!("expected Raw, got {value:?}"),
829        }
830        assert_eq!(
831            attributes.encode(AsnLength::Bits16),
832            Bytes::from_static(&[0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc])
833        );
834    }
835
836    #[test]
837    fn test_deprecated_code_13_retained_as_deprecated() {
838        let data = Bytes::from(vec![0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04]);
839        let attributes =
840            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
841
842        assert_eq!(attributes.inner.len(), 1);
843        match &attributes.inner[0].value {
844            AttributeValue::Deprecated(raw) => {
845                assert_eq!(raw.code, 13);
846                assert_eq!(raw.attr_type(), AttrType::Unknown(13));
847                assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
848            }
849            value => panic!("expected Deprecated, got {value:?}"),
850        }
851        assert_eq!(
852            attributes.encode(AsnLength::Bits16),
853            Bytes::from_static(&[0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04])
854        );
855    }
856
857    #[test]
858    fn test_malformed_typed_attribute_falls_back_to_raw() {
859        let data = Bytes::from(vec![0x40, 0x03, 0x03, 0x01, 0x02, 0x03]); // NEXT_HOP length must be 4
860        let attributes =
861            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
862
863        assert!(attributes.has_validation_warnings());
864        match &attributes.inner[0].value {
865            AttributeValue::Raw(raw) => {
866                assert_eq!(raw.code, 3);
867                assert_eq!(raw.attr_type(), AttrType::NEXT_HOP);
868                assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03]));
869            }
870            value => panic!("expected Raw fallback, got {value:?}"),
871        }
872        assert_eq!(
873            attributes.encode(AsnLength::Bits16),
874            Bytes::from_static(&[0x40, 0x03, 0x03, 0x01, 0x02, 0x03])
875        );
876    }
877
878    #[test]
879    fn test_all_raw_retained_attribute_codes_parse_and_round_trip() {
880        let raw_codes = [0, 22, 24, 27, 33, 128];
881
882        for code in raw_codes {
883            let wire = vec![0xc0, code, 0x02, 0xaa, 0xbb];
884            let attributes = parse_attributes(
885                Bytes::from(wire.clone()),
886                &AsnLength::Bits16,
887                false,
888                None,
889                None,
890                None,
891            )
892            .unwrap();
893            assert_eq!(attributes.inner.len(), 1, "code {code}");
894            match &attributes.inner[0].value {
895                AttributeValue::Raw(raw) => {
896                    assert_eq!(raw.code, code);
897                    assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
898                }
899                value => panic!("expected Raw for code {code}, got {value:?}"),
900            }
901            assert!(attributes.has_attr(AttrType::from(code)), "code {code}");
902            assert_eq!(attributes.encode(AsnLength::Bits16), Bytes::from(wire));
903        }
904    }
905
906    #[test]
907    fn test_unassigned_attribute_code_retained_as_unknown() {
908        let wire = vec![0xc0, 0x7f, 0x02, 0xaa, 0xbb];
909        let attributes = parse_attributes(
910            Bytes::from(wire.clone()),
911            &AsnLength::Bits16,
912            false,
913            None,
914            None,
915            None,
916        )
917        .unwrap();
918
919        assert_eq!(attributes.inner.len(), 1);
920        match &attributes.inner[0].value {
921            AttributeValue::Unknown(raw) => {
922                assert_eq!(raw.code, 0x7f);
923                assert_eq!(raw.attr_type(), AttrType::Unknown(0x7f));
924                assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
925            }
926            value => panic!("expected Unknown, got {value:?}"),
927        }
928        assert!(attributes.has_attr(AttrType::Unknown(0x7f)));
929        assert_eq!(attributes.encode(AsnLength::Bits16), Bytes::from(wire));
930    }
931
932    #[test]
933    fn test_structured_tlv_attributes_parse_and_round_trip() {
934        let cases = [
935            (
936                vec![0xc0, 0x26, 0x05, 0x01, 0x01, 0x02, 0x03, 0x04],
937                "BFD Discriminator",
938            ),
939            (
940                vec![0xc0, 0x28, 0x05, 0x7f, 0x00, 0x02, 0xaa, 0xbb],
941                "BGP Prefix-SID",
942            ),
943            (
944                vec![0xc0, 0x29, 0x06, 0x12, 0x34, 0x00, 0x02, 0xde, 0xad],
945                "BIER",
946            ),
947            (vec![0xc0, 0x25, 0x05, 0x7f, 0x00, 0x02, 0xde, 0xad], "SFP"),
948        ];
949
950        for (wire, name) in cases {
951            let data = Bytes::from(wire.clone());
952            let attributes =
953                parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
954            assert_eq!(attributes.inner.len(), 1, "{name}");
955            match (name, &attributes.inner[0].value) {
956                ("BFD Discriminator", AttributeValue::BfdDiscriminator(_))
957                | ("BGP Prefix-SID", AttributeValue::BgpPrefixSid(_))
958                | ("BIER", AttributeValue::Bier(_))
959                | ("SFP", AttributeValue::Sfp(_)) => {}
960                (_, value) => panic!("unexpected value for {name}: {value:?}"),
961            }
962            assert_eq!(
963                attributes.encode(AsnLength::Bits16),
964                Bytes::from(wire),
965                "{name}"
966            );
967        }
968    }
969
970    #[test]
971    fn test_rfc7606_attribute_length_error() {
972        // Create an ORIGIN attribute with wrong length (should be 1 byte, not 2)
973        let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
974        let asn_len = AsnLength::Bits16;
975        let add_path = false;
976        let afi = None;
977        let safi = None;
978        let prefixes = None;
979
980        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
981
982        // Should have warning for incorrect attribute length
983        assert!(attributes.has_validation_warnings());
984        let warnings = attributes.validation_warnings();
985
986        let has_length_warning = warnings
987            .iter()
988            .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
989        assert!(has_length_warning);
990    }
991
992    #[test]
993    fn test_rfc7606_no_session_reset() {
994        // Test that parsing continues even with multiple errors
995        let data = Bytes::from(vec![
996            0x80, 0x01, 0x02, 0x00, 0x01, // Wrong flags and length for ORIGIN
997            0x40, 0x01, 0x01, 0x00, // Duplicate ORIGIN
998            0x40, 0xFF, 0x01, 0x00, // Unknown attribute
999        ]);
1000        let asn_len = AsnLength::Bits16;
1001        let add_path = false;
1002        let afi = None;
1003        let safi = None;
1004        let prefixes = None;
1005
1006        // Should not panic or return error - RFC 7606 requires continued parsing
1007        let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
1008        assert!(result.is_ok());
1009
1010        let attributes = result.unwrap();
1011        assert!(attributes.has_validation_warnings());
1012
1013        // Should have multiple warnings but parsing should continue
1014        let warnings = attributes.validation_warnings();
1015        assert!(!warnings.is_empty());
1016    }
1017}