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_29_linkstate;
14mod attr_32_large_communities;
15mod attr_35_otc;
16
17use bytes::{Buf, BufMut, Bytes, BytesMut};
18use log::{debug, warn};
19use std::net::IpAddr;
20
21use crate::models::*;
22
23use crate::error::{BgpValidationWarning, ParserError};
24use crate::parser::bgp::attributes::attr_01_origin::{encode_origin, parse_origin};
25use crate::parser::bgp::attributes::attr_02_17_as_path::{encode_as_path, parse_as_path};
26use crate::parser::bgp::attributes::attr_03_next_hop::{encode_next_hop, parse_next_hop};
27use crate::parser::bgp::attributes::attr_04_med::{encode_med, parse_med};
28use crate::parser::bgp::attributes::attr_05_local_pref::{encode_local_pref, parse_local_pref};
29use crate::parser::bgp::attributes::attr_07_18_aggregator::{encode_aggregator, parse_aggregator};
30use crate::parser::bgp::attributes::attr_08_communities::{
31    encode_regular_communities, parse_regular_communities,
32};
33use crate::parser::bgp::attributes::attr_09_originator::{
34    encode_originator_id, parse_originator_id,
35};
36use crate::parser::bgp::attributes::attr_10_13_cluster::{encode_clusters, parse_clusters};
37use crate::parser::bgp::attributes::attr_14_15_nlri::{encode_nlri, parse_nlri};
38use crate::parser::bgp::attributes::attr_16_25_extended_communities::{
39    encode_extended_communities, encode_ipv6_extended_communities, parse_extended_community,
40    parse_ipv6_extended_community,
41};
42use crate::parser::bgp::attributes::attr_23_tunnel_encap::{
43    encode_tunnel_encapsulation_attribute, parse_tunnel_encapsulation_attribute,
44};
45use crate::parser::bgp::attributes::attr_29_linkstate::{
46    encode_link_state_attribute, parse_link_state_attribute,
47};
48use crate::parser::bgp::attributes::attr_32_large_communities::{
49    encode_large_communities, parse_large_communities,
50};
51use crate::parser::bgp::attributes::attr_35_otc::{
52    encode_only_to_customer, parse_only_to_customer,
53};
54use crate::parser::ReadUtils;
55
56/// Validate attribute flags according to RFC 4271 and RFC 7606
57fn validate_attribute_flags(
58    attr_type: AttrType,
59    flags: AttrFlags,
60    warnings: &mut Vec<BgpValidationWarning>,
61) {
62    let expected_flags = match attr_type {
63        // Well-known mandatory attributes
64        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP => AttrFlags::TRANSITIVE,
65        // Well-known discretionary attributes
66        AttrType::ATOMIC_AGGREGATE => AttrFlags::TRANSITIVE,
67        // Optional non-transitive attributes
68        AttrType::MULTI_EXIT_DISCRIMINATOR
69        | AttrType::ORIGINATOR_ID
70        | AttrType::CLUSTER_LIST
71        | AttrType::MP_REACHABLE_NLRI
72        | AttrType::MP_UNREACHABLE_NLRI => AttrFlags::OPTIONAL,
73        // Optional transitive attributes
74        AttrType::AGGREGATOR
75        | AttrType::AS4_AGGREGATOR
76        | AttrType::AS4_PATH
77        | AttrType::COMMUNITIES
78        | AttrType::EXTENDED_COMMUNITIES
79        | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
80        | AttrType::LARGE_COMMUNITIES
81        | AttrType::ONLY_TO_CUSTOMER => AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE,
82        // LOCAL_PREFERENCE is well-known mandatory for IBGP
83        AttrType::LOCAL_PREFERENCE => AttrFlags::TRANSITIVE,
84        // Unknown or development attributes
85        _ => return, // Don't validate unknown attributes
86    };
87
88    // Check if flags match expected (ignoring EXTENDED and PARTIAL flags for this check)
89    let relevant_flags = flags & (AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE);
90    if relevant_flags != expected_flags {
91        warnings.push(BgpValidationWarning::AttributeFlagsError {
92            attr_type,
93            expected_flags: expected_flags.bits(),
94            actual_flags: relevant_flags.bits(),
95        });
96    }
97
98    // Check partial flag constraint
99    if flags.contains(AttrFlags::PARTIAL) {
100        match attr_type {
101            // Partial bit MUST be 0 for well-known attributes and optional non-transitive
102            AttrType::ORIGIN
103            | AttrType::AS_PATH
104            | AttrType::NEXT_HOP
105            | AttrType::LOCAL_PREFERENCE
106            | AttrType::ATOMIC_AGGREGATE
107            | AttrType::MULTI_EXIT_DISCRIMINATOR
108            | AttrType::ORIGINATOR_ID
109            | AttrType::CLUSTER_LIST
110            | AttrType::MP_REACHABLE_NLRI
111            | AttrType::MP_UNREACHABLE_NLRI => {
112                warnings.push(BgpValidationWarning::AttributeFlagsError {
113                    attr_type,
114                    expected_flags: expected_flags.bits(),
115                    actual_flags: flags.bits(),
116                });
117            }
118            _ => {} // Partial is OK for optional transitive attributes
119        }
120    }
121}
122
123/// Check if an attribute type is well-known mandatory
124fn is_well_known_mandatory(attr_type: AttrType) -> bool {
125    matches!(
126        attr_type,
127        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP | AttrType::LOCAL_PREFERENCE
128    )
129}
130
131/// Validate attribute length constraints
132fn validate_attribute_length(
133    attr_type: AttrType,
134    length: usize,
135    warnings: &mut Vec<BgpValidationWarning>,
136) {
137    let expected_length = match attr_type {
138        AttrType::ORIGIN => Some(1),
139        AttrType::NEXT_HOP => Some(4), // IPv4 next hop
140        AttrType::MULTI_EXIT_DISCRIMINATOR => Some(4),
141        AttrType::LOCAL_PREFERENCE => Some(4),
142        AttrType::ATOMIC_AGGREGATE => Some(0),
143        AttrType::ORIGINATOR_ID => Some(4),
144        AttrType::ONLY_TO_CUSTOMER => Some(4),
145        // Variable length attributes - no fixed constraint
146        AttrType::AS_PATH
147        | AttrType::AS4_PATH
148        | AttrType::AGGREGATOR
149        | AttrType::AS4_AGGREGATOR
150        | AttrType::COMMUNITIES
151        | AttrType::EXTENDED_COMMUNITIES
152        | AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
153        | AttrType::LARGE_COMMUNITIES
154        | AttrType::CLUSTER_LIST
155        | AttrType::MP_REACHABLE_NLRI
156        | AttrType::MP_UNREACHABLE_NLRI => None,
157        _ => None, // Unknown attributes
158    };
159
160    if let Some(expected) = expected_length {
161        if length != expected {
162            warnings.push(BgpValidationWarning::AttributeLengthError {
163                attr_type,
164                expected_length: Some(expected),
165                actual_length: length,
166            });
167        }
168    }
169}
170
171/// Parse BGP attributes given a slice of u8 and some options.
172///
173/// The `data: &[u8]` contains the entirety of the attributes bytes, therefore the size of
174/// the slice is the total byte length of the attributes section of the message.
175pub fn parse_attributes(
176    mut data: Bytes,
177    asn_len: &AsnLength,
178    add_path: bool,
179    afi: Option<Afi>,
180    safi: Option<Safi>,
181    prefixes: Option<&[NetworkPrefix]>,
182) -> Result<Attributes, ParserError> {
183    // Estimate capacity from data size: each attribute is at least 3 bytes
184    // (flag + type + length). Cap at 256 to avoid over-allocation for corrupted data.
185    let estimated_attrs = (data.remaining() / 3).min(256);
186    let mut attributes: Vec<Attribute> = Vec::with_capacity(estimated_attrs.max(8));
187    let mut validation_warnings: Vec<BgpValidationWarning> = Vec::new();
188    // boolean flags for seen attributes - small dataset in hot loop.
189    let mut seen_attributes: [bool; 256] = [false; 256];
190
191    while data.remaining() >= 3 {
192        // each attribute is at least 3 bytes: flag(1) + type(1) + length(1)
193        // thus the while loop condition is set to be at least 3 bytes to read.
194
195        // has content to read
196        let flag = AttrFlags::from_bits_retain(data.read_u8()?);
197        let attr_type = data.read_u8()?;
198        let attr_length = match flag.contains(AttrFlags::EXTENDED) {
199            false => data.read_u8()? as usize,
200            true => data.read_u16()? as usize,
201        };
202
203        let mut partial = false;
204        if flag.contains(AttrFlags::PARTIAL) {
205            /*
206                https://datatracker.ietf.org/doc/html/rfc4271#section-4.3
207
208            > The third high-order bit (bit 2) of the Attribute Flags octet
209            > is the Partial bit.  It defines whether the information
210            > contained in the optional transitive attribute is partial (if
211            > set to 1) or complete (if set to 0).  For well-known attributes
212            > and for optional non-transitive attributes, the Partial bit
213            > MUST be set to 0.
214            */
215            partial = true;
216        }
217
218        debug!(
219            "reading attribute: type -- {:?}, length -- {}",
220            &attr_type, attr_length
221        );
222
223        let parsed_attr_type = AttrType::from(attr_type);
224
225        // RFC 7606: Check for duplicate attributes
226        if seen_attributes[attr_type as usize] {
227            validation_warnings.push(BgpValidationWarning::DuplicateAttribute {
228                attr_type: parsed_attr_type,
229            });
230            // Continue processing - don't skip duplicate for now
231        }
232        seen_attributes[attr_type as usize] = true;
233
234        // Validate attribute flags and length
235        validate_attribute_flags(parsed_attr_type, flag, &mut validation_warnings);
236        validate_attribute_length(parsed_attr_type, attr_length, &mut validation_warnings);
237
238        let attr_type = match parsed_attr_type {
239            attr_type @ AttrType::Unknown(unknown_type) => {
240                // skip pass the remaining bytes of this attribute
241                let bytes = data.read_n_bytes(attr_length)?;
242                let attr_value = match get_deprecated_attr_type(unknown_type) {
243                    Some(t) => {
244                        debug!("deprecated attribute type: {} - {}", unknown_type, t);
245                        AttributeValue::Deprecated(AttrRaw { attr_type, bytes })
246                    }
247                    None => {
248                        debug!("unknown attribute type: {}", unknown_type);
249                        AttributeValue::Unknown(AttrRaw { attr_type, bytes })
250                    }
251                };
252
253                assert_eq!(attr_type, attr_value.attr_type());
254                attributes.push(Attribute {
255                    value: attr_value,
256                    flag,
257                });
258                continue;
259            }
260            t => t,
261        };
262
263        let bytes_left = data.remaining();
264
265        if data.remaining() < attr_length {
266            warn!(
267                "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
268                attr_type, attr_length, bytes_left
269            );
270            // break and return already parsed attributes
271            break;
272        }
273
274        // we know data has enough bytes to read, so we can split the bytes into a new Bytes object
275        data.has_n_remaining(attr_length)?;
276        let mut attr_data = data.split_to(attr_length);
277
278        let attr = match attr_type {
279            AttrType::ORIGIN => parse_origin(attr_data),
280            AttrType::AS_PATH => {
281                parse_as_path(attr_data, asn_len).map(|path| AttributeValue::AsPath {
282                    path,
283                    is_as4: false,
284                })
285            }
286            AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
287            AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
288            AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
289            AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
290            AttrType::AGGREGATOR => {
291                parse_aggregator(attr_data, asn_len).map(|(asn, id)| AttributeValue::Aggregator {
292                    asn,
293                    id,
294                    is_as4: false,
295                })
296            }
297            AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
298            AttrType::CLUSTER_LIST => parse_clusters(attr_data),
299            AttrType::MP_REACHABLE_NLRI => {
300                parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
301            }
302            AttrType::MP_UNREACHABLE_NLRI => {
303                parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
304            }
305            AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32)
306                .map(|path| AttributeValue::AsPath { path, is_as4: true }),
307            AttrType::AS4_AGGREGATOR => {
308                parse_aggregator(attr_data, &AsnLength::Bits32).map(|(asn, id)| {
309                    AttributeValue::Aggregator {
310                        asn,
311                        id,
312                        is_as4: true,
313                    }
314                })
315            }
316
317            // communities
318            AttrType::COMMUNITIES => parse_regular_communities(attr_data),
319            AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
320            AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
321            AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
322                parse_ipv6_extended_community(attr_data)
323            }
324            AttrType::DEVELOPMENT => {
325                let mut value = vec![];
326                for _i in 0..attr_length {
327                    value.push(attr_data.read_u8()?);
328                }
329                Ok(AttributeValue::Development(value))
330            }
331            AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
332            AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
333            AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
334            _ => Err(ParserError::Unsupported(format!(
335                "unsupported attribute type: {attr_type:?}"
336            ))),
337        };
338
339        match attr {
340            Ok(value) => {
341                assert_eq!(attr_type, value.attr_type());
342                attributes.push(Attribute { value, flag });
343            }
344            Err(e) => {
345                // RFC 7606 error handling
346                if partial {
347                    // Partial attribute with errors - log warning but continue
348                    validation_warnings.push(BgpValidationWarning::PartialAttributeError {
349                        attr_type,
350                        reason: e.to_string(),
351                    });
352                    debug!("PARTIAL attribute error: {}", e);
353                } else if is_well_known_mandatory(attr_type) {
354                    // For well-known mandatory attributes, use "treat-as-withdraw" approach
355                    // Don't break parsing, but log warning
356                    validation_warnings.push(BgpValidationWarning::MalformedAttributeList {
357                        reason: format!(
358                            "Well-known mandatory attribute {} parsing failed: {}",
359                            u8::from(attr_type),
360                            e
361                        ),
362                    });
363                    debug!(
364                        "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
365                        e
366                    );
367                } else {
368                    // For optional attributes, use "attribute discard" approach
369                    validation_warnings.push(BgpValidationWarning::OptionalAttributeError {
370                        attr_type,
371                        reason: e.to_string(),
372                    });
373                    debug!("Optional attribute error, discarding: {}", e);
374                }
375                // Continue parsing in all cases - never break the session
376                continue;
377            }
378        };
379    }
380
381    // Check for missing well-known mandatory attributes
382    let mandatory_attributes = [
383        AttrType::ORIGIN,
384        AttrType::AS_PATH,
385        AttrType::NEXT_HOP,
386        // LOCAL_PREFERENCE is only mandatory for IBGP, so we don't check it here
387    ];
388
389    for &mandatory_attr in &mandatory_attributes {
390        if !seen_attributes[u8::from(mandatory_attr) as usize] {
391            validation_warnings.push(BgpValidationWarning::MissingWellKnownAttribute {
392                attr_type: mandatory_attr,
393            });
394        }
395    }
396
397    let mut result = Attributes::from(attributes);
398    result.validation_warnings = validation_warnings;
399    Ok(result)
400}
401
402impl Attribute {
403    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
404        let mut bytes = BytesMut::new();
405
406        let flag = self.flag.bits();
407        let type_code = self.value.attr_type().into();
408
409        bytes.put_u8(flag);
410        bytes.put_u8(type_code);
411
412        let value_bytes = match &self.value {
413            AttributeValue::Origin(v) => encode_origin(v),
414            AttributeValue::AsPath { path, is_as4 } => {
415                let four_byte = match is_as4 {
416                    true => AsnLength::Bits32,
417                    false => match asn_len.is_four_byte() {
418                        true => AsnLength::Bits32,
419                        false => AsnLength::Bits16,
420                    },
421                };
422                encode_as_path(path, four_byte)
423            }
424            AttributeValue::NextHop(v) => encode_next_hop(v),
425            AttributeValue::MultiExitDiscriminator(v) => encode_med(*v),
426            AttributeValue::LocalPreference(v) => encode_local_pref(*v),
427            AttributeValue::OnlyToCustomer(v) => encode_only_to_customer(v.into()),
428            AttributeValue::AtomicAggregate => Bytes::default(),
429            AttributeValue::Aggregator { asn, id, is_as4: _ } => {
430                encode_aggregator(asn, &IpAddr::from(*id))
431            }
432            AttributeValue::Communities(v) => encode_regular_communities(v),
433            AttributeValue::ExtendedCommunities(v) => encode_extended_communities(v),
434            AttributeValue::LargeCommunities(v) => encode_large_communities(v),
435            AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
436                encode_ipv6_extended_communities(v)
437            }
438            AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
439            AttributeValue::Clusters(v) => encode_clusters(v),
440            AttributeValue::MpReachNlri(v) => encode_nlri(v, true),
441            AttributeValue::MpUnreachNlri(v) => encode_nlri(v, false),
442            AttributeValue::LinkState(v) => encode_link_state_attribute(v),
443            AttributeValue::TunnelEncapsulation(v) => encode_tunnel_encapsulation_attribute(v),
444            AttributeValue::Development(v) => Bytes::from(v.to_owned()),
445            AttributeValue::Deprecated(v) => Bytes::from(v.bytes.to_owned()),
446            AttributeValue::Unknown(v) => Bytes::from(v.bytes.to_owned()),
447            AttributeValue::Aigp(_v) => {
448                // AIGP encoding not yet implemented - return empty bytes
449                Bytes::new()
450            }
451            AttributeValue::AttrSet(_v) => {
452                // ATTR_SET encoding not yet implemented - return empty bytes
453                Bytes::new()
454            }
455        };
456
457        match self.is_extended() {
458            false => {
459                bytes.put_u8(value_bytes.len() as u8);
460            }
461            true => {
462                bytes.put_u16(value_bytes.len() as u16);
463            }
464        }
465        bytes.extend(value_bytes);
466        bytes.freeze()
467    }
468}
469
470impl Attributes {
471    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
472        let mut bytes = BytesMut::new();
473        for attr in &self.inner {
474            bytes.extend(attr.encode(asn_len));
475        }
476        bytes.freeze()
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    #[test]
485    fn test_unknwon_attribute_type() {
486        let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
487        let asn_len = AsnLength::Bits16;
488        let add_path = false;
489        let afi = None;
490        let safi = None;
491        let prefixes = None;
492        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
493        assert!(attributes.is_ok());
494        let attributes = attributes.unwrap();
495        assert_eq!(attributes.inner.len(), 1);
496        assert_eq!(
497            attributes.inner[0].value.attr_type(),
498            AttrType::Unknown(254)
499        );
500    }
501
502    #[test]
503    fn test_rfc7606_attribute_flags_error() {
504        // Create an ORIGIN attribute with wrong flags (should be transitive, not optional)
505        let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); // Optional flag set incorrectly
506        let asn_len = AsnLength::Bits16;
507        let add_path = false;
508        let afi = None;
509        let safi = None;
510        let prefixes = None;
511
512        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
513
514        // Should have validation warning for incorrect flags
515        assert!(attributes.has_validation_warnings());
516        let warnings = attributes.validation_warnings();
517        // Will have attribute flags error + missing mandatory attributes
518        assert!(!warnings.is_empty());
519
520        match &warnings[0] {
521            BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
522                assert_eq!(*attr_type, AttrType::ORIGIN);
523            }
524            _ => panic!("Expected AttributeFlagsError warning"),
525        }
526    }
527
528    #[test]
529    fn test_rfc7606_missing_mandatory_attribute() {
530        // Empty attributes - should have warnings for missing mandatory attributes
531        let data = Bytes::from(vec![]);
532        let asn_len = AsnLength::Bits16;
533        let add_path = false;
534        let afi = None;
535        let safi = None;
536        let prefixes = None;
537
538        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
539
540        // Should have warnings for missing mandatory attributes
541        assert!(attributes.has_validation_warnings());
542        let warnings = attributes.validation_warnings();
543        assert_eq!(warnings.len(), 3); // ORIGIN, AS_PATH, NEXT_HOP
544
545        for warning in warnings {
546            match warning {
547                BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
548                    assert!(matches!(
549                        attr_type,
550                        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
551                    ));
552                }
553                _ => panic!("Expected MissingWellKnownAttribute warning"),
554            }
555        }
556    }
557
558    #[test]
559    fn test_rfc7606_duplicate_attribute() {
560        // Create two ORIGIN attributes
561        let data = Bytes::from(vec![
562            0x40, 0x01, 0x01, 0x00, // First ORIGIN attribute
563            0x40, 0x01, 0x01, 0x01, // Second ORIGIN attribute (duplicate)
564        ]);
565        let asn_len = AsnLength::Bits16;
566        let add_path = false;
567        let afi = None;
568        let safi = None;
569        let prefixes = None;
570
571        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
572
573        // Should have warning for duplicate attribute
574        assert!(attributes.has_validation_warnings());
575        let warnings = attributes.validation_warnings();
576
577        // Should have at least one duplicate attribute warning
578        let has_duplicate_warning = warnings
579            .iter()
580            .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
581        assert!(has_duplicate_warning);
582    }
583
584    #[test]
585    fn test_attribute_type_boundaries() {
586        let asn_len = AsnLength::Bits16;
587        let add_path = false;
588        let afi = None;
589        let safi = None;
590        let prefixes = None;
591
592        // Required attributes for valid BGP message
593        const REQUIRED_ATTRS: &[u8] = &[
594            0x40, 0x01, 0x01, 0x00, // origin
595            0x40, 0x02, 0x00, // as_path
596            0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, // next_hop
597        ];
598
599        // Test highest (development) attribute type
600        let mut data = REQUIRED_ATTRS.to_vec();
601        data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); // development
602        let data = Bytes::from(data);
603
604        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
605
606        assert!(attributes.has_attr(AttrType::DEVELOPMENT));
607        assert!(!attributes.has_validation_warnings());
608
609        // Test lowest (reserved) attribute type
610        let mut data = REQUIRED_ATTRS.to_vec();
611        data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); // reserved
612        let data = Bytes::from(data);
613
614        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
615
616        // There is a validation warning about the reserved attribute
617        assert!(attributes.validation_warnings.iter().any(|vw| {
618            matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
619        }));
620    }
621
622    #[test]
623    fn test_rfc7606_attribute_length_error() {
624        // Create an ORIGIN attribute with wrong length (should be 1 byte, not 2)
625        let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
626        let asn_len = AsnLength::Bits16;
627        let add_path = false;
628        let afi = None;
629        let safi = None;
630        let prefixes = None;
631
632        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
633
634        // Should have warning for incorrect attribute length
635        assert!(attributes.has_validation_warnings());
636        let warnings = attributes.validation_warnings();
637
638        let has_length_warning = warnings
639            .iter()
640            .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
641        assert!(has_length_warning);
642    }
643
644    #[test]
645    fn test_rfc7606_no_session_reset() {
646        // Test that parsing continues even with multiple errors
647        let data = Bytes::from(vec![
648            0x80, 0x01, 0x02, 0x00, 0x01, // Wrong flags and length for ORIGIN
649            0x40, 0x01, 0x01, 0x00, // Duplicate ORIGIN
650            0x40, 0xFF, 0x01, 0x00, // Unknown attribute
651        ]);
652        let asn_len = AsnLength::Bits16;
653        let add_path = false;
654        let afi = None;
655        let safi = None;
656        let prefixes = None;
657
658        // Should not panic or return error - RFC 7606 requires continued parsing
659        let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
660        assert!(result.is_ok());
661
662        let attributes = result.unwrap();
663        assert!(attributes.has_validation_warnings());
664
665        // Should have multiple warnings but parsing should continue
666        let warnings = attributes.validation_warnings();
667        assert!(!warnings.is_empty());
668    }
669}