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    let mut attributes: Vec<Attribute> = Vec::with_capacity(20);
184    let mut validation_warnings: Vec<BgpValidationWarning> = Vec::new();
185    // boolean flags for seen attributes - small dataset in hot loop.
186    let mut seen_attributes: [bool; 256] = [false; 256];
187
188    while data.remaining() >= 3 {
189        // each attribute is at least 3 bytes: flag(1) + type(1) + length(1)
190        // thus the while loop condition is set to be at least 3 bytes to read.
191
192        // has content to read
193        let flag = AttrFlags::from_bits_retain(data.get_u8());
194        let attr_type = data.get_u8();
195        let attr_length = match flag.contains(AttrFlags::EXTENDED) {
196            false => data.get_u8() as usize,
197            true => data.get_u16() as usize,
198        };
199
200        let mut partial = false;
201        if flag.contains(AttrFlags::PARTIAL) {
202            /*
203                https://datatracker.ietf.org/doc/html/rfc4271#section-4.3
204
205            > The third high-order bit (bit 2) of the Attribute Flags octet
206            > is the Partial bit.  It defines whether the information
207            > contained in the optional transitive attribute is partial (if
208            > set to 1) or complete (if set to 0).  For well-known attributes
209            > and for optional non-transitive attributes, the Partial bit
210            > MUST be set to 0.
211            */
212            partial = true;
213        }
214
215        debug!(
216            "reading attribute: type -- {:?}, length -- {}",
217            &attr_type, attr_length
218        );
219
220        let parsed_attr_type = AttrType::from(attr_type);
221
222        // RFC 7606: Check for duplicate attributes
223        if seen_attributes[attr_type as usize] {
224            validation_warnings.push(BgpValidationWarning::DuplicateAttribute {
225                attr_type: parsed_attr_type,
226            });
227            // Continue processing - don't skip duplicate for now
228        }
229        seen_attributes[attr_type as usize] = true;
230
231        // Validate attribute flags and length
232        validate_attribute_flags(parsed_attr_type, flag, &mut validation_warnings);
233        validate_attribute_length(parsed_attr_type, attr_length, &mut validation_warnings);
234
235        let attr_type = match parsed_attr_type {
236            attr_type @ AttrType::Unknown(unknown_type) => {
237                // skip pass the remaining bytes of this attribute
238                let bytes = data.read_n_bytes(attr_length)?;
239                let attr_value = match get_deprecated_attr_type(unknown_type) {
240                    Some(t) => {
241                        debug!("deprecated attribute type: {} - {}", unknown_type, t);
242                        AttributeValue::Deprecated(AttrRaw { attr_type, bytes })
243                    }
244                    None => {
245                        debug!("unknown attribute type: {}", unknown_type);
246                        AttributeValue::Unknown(AttrRaw { attr_type, bytes })
247                    }
248                };
249
250                assert_eq!(attr_type, attr_value.attr_type());
251                attributes.push(Attribute {
252                    value: attr_value,
253                    flag,
254                });
255                continue;
256            }
257            t => t,
258        };
259
260        let bytes_left = data.remaining();
261
262        if data.remaining() < attr_length {
263            warn!(
264                "not enough bytes: input bytes left - {}, want to read - {}; skipping",
265                bytes_left, attr_length
266            );
267            // break and return already parsed attributes
268            break;
269        }
270
271        // we know data has enough bytes to read, so we can split the bytes into a new Bytes object
272        data.has_n_remaining(attr_length)?;
273        let mut attr_data = data.split_to(attr_length);
274
275        let attr = match attr_type {
276            AttrType::ORIGIN => parse_origin(attr_data),
277            AttrType::AS_PATH => {
278                parse_as_path(attr_data, asn_len).map(|path| AttributeValue::AsPath {
279                    path,
280                    is_as4: false,
281                })
282            }
283            AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
284            AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
285            AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
286            AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
287            AttrType::AGGREGATOR => {
288                parse_aggregator(attr_data, asn_len).map(|(asn, id)| AttributeValue::Aggregator {
289                    asn,
290                    id,
291                    is_as4: false,
292                })
293            }
294            AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
295            AttrType::CLUSTER_LIST => parse_clusters(attr_data),
296            AttrType::MP_REACHABLE_NLRI => {
297                parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
298            }
299            AttrType::MP_UNREACHABLE_NLRI => {
300                parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
301            }
302            AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32)
303                .map(|path| AttributeValue::AsPath { path, is_as4: true }),
304            AttrType::AS4_AGGREGATOR => {
305                parse_aggregator(attr_data, &AsnLength::Bits32).map(|(asn, id)| {
306                    AttributeValue::Aggregator {
307                        asn,
308                        id,
309                        is_as4: true,
310                    }
311                })
312            }
313
314            // communities
315            AttrType::COMMUNITIES => parse_regular_communities(attr_data),
316            AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
317            AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
318            AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
319                parse_ipv6_extended_community(attr_data)
320            }
321            AttrType::DEVELOPMENT => {
322                let mut value = vec![];
323                for _i in 0..attr_length {
324                    value.push(attr_data.get_u8());
325                }
326                Ok(AttributeValue::Development(value))
327            }
328            AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
329            AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
330            AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
331            _ => Err(ParserError::Unsupported(format!(
332                "unsupported attribute type: {attr_type:?}"
333            ))),
334        };
335
336        match attr {
337            Ok(value) => {
338                assert_eq!(attr_type, value.attr_type());
339                attributes.push(Attribute { value, flag });
340            }
341            Err(e) => {
342                // RFC 7606 error handling
343                if partial {
344                    // Partial attribute with errors - log warning but continue
345                    validation_warnings.push(BgpValidationWarning::PartialAttributeError {
346                        attr_type,
347                        reason: e.to_string(),
348                    });
349                    debug!("PARTIAL attribute error: {}", e);
350                } else if is_well_known_mandatory(attr_type) {
351                    // For well-known mandatory attributes, use "treat-as-withdraw" approach
352                    // Don't break parsing, but log warning
353                    validation_warnings.push(BgpValidationWarning::MalformedAttributeList {
354                        reason: format!(
355                            "Well-known mandatory attribute {} parsing failed: {}",
356                            u8::from(attr_type),
357                            e
358                        ),
359                    });
360                    debug!(
361                        "Well-known mandatory attribute parsing failed, treating as withdraw: {}",
362                        e
363                    );
364                } else {
365                    // For optional attributes, use "attribute discard" approach
366                    validation_warnings.push(BgpValidationWarning::OptionalAttributeError {
367                        attr_type,
368                        reason: e.to_string(),
369                    });
370                    debug!("Optional attribute error, discarding: {}", e);
371                }
372                // Continue parsing in all cases - never break the session
373                continue;
374            }
375        };
376    }
377
378    // Check for missing well-known mandatory attributes
379    let mandatory_attributes = [
380        AttrType::ORIGIN,
381        AttrType::AS_PATH,
382        AttrType::NEXT_HOP,
383        // LOCAL_PREFERENCE is only mandatory for IBGP, so we don't check it here
384    ];
385
386    for &mandatory_attr in &mandatory_attributes {
387        if !seen_attributes[u8::from(mandatory_attr) as usize] {
388            validation_warnings.push(BgpValidationWarning::MissingWellKnownAttribute {
389                attr_type: mandatory_attr,
390            });
391        }
392    }
393
394    let mut result = Attributes::from(attributes);
395    result.validation_warnings = validation_warnings;
396    Ok(result)
397}
398
399impl Attribute {
400    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
401        let mut bytes = BytesMut::new();
402
403        let flag = self.flag.bits();
404        let type_code = self.value.attr_type().into();
405
406        bytes.put_u8(flag);
407        bytes.put_u8(type_code);
408
409        let value_bytes = match &self.value {
410            AttributeValue::Origin(v) => encode_origin(v),
411            AttributeValue::AsPath { path, is_as4 } => {
412                let four_byte = match is_as4 {
413                    true => AsnLength::Bits32,
414                    false => match asn_len.is_four_byte() {
415                        true => AsnLength::Bits32,
416                        false => AsnLength::Bits16,
417                    },
418                };
419                encode_as_path(path, four_byte)
420            }
421            AttributeValue::NextHop(v) => encode_next_hop(v),
422            AttributeValue::MultiExitDiscriminator(v) => encode_med(*v),
423            AttributeValue::LocalPreference(v) => encode_local_pref(*v),
424            AttributeValue::OnlyToCustomer(v) => encode_only_to_customer(v.into()),
425            AttributeValue::AtomicAggregate => Bytes::default(),
426            AttributeValue::Aggregator { asn, id, is_as4: _ } => {
427                encode_aggregator(asn, &IpAddr::from(*id))
428            }
429            AttributeValue::Communities(v) => encode_regular_communities(v),
430            AttributeValue::ExtendedCommunities(v) => encode_extended_communities(v),
431            AttributeValue::LargeCommunities(v) => encode_large_communities(v),
432            AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
433                encode_ipv6_extended_communities(v)
434            }
435            AttributeValue::OriginatorId(v) => encode_originator_id(&IpAddr::from(*v)),
436            AttributeValue::Clusters(v) => encode_clusters(v),
437            AttributeValue::MpReachNlri(v) => encode_nlri(v, true),
438            AttributeValue::MpUnreachNlri(v) => encode_nlri(v, false),
439            AttributeValue::LinkState(v) => encode_link_state_attribute(v),
440            AttributeValue::TunnelEncapsulation(v) => encode_tunnel_encapsulation_attribute(v),
441            AttributeValue::Development(v) => Bytes::from(v.to_owned()),
442            AttributeValue::Deprecated(v) => Bytes::from(v.bytes.to_owned()),
443            AttributeValue::Unknown(v) => Bytes::from(v.bytes.to_owned()),
444        };
445
446        match self.is_extended() {
447            false => {
448                bytes.put_u8(value_bytes.len() as u8);
449            }
450            true => {
451                bytes.put_u16(value_bytes.len() as u16);
452            }
453        }
454        bytes.extend(value_bytes);
455        bytes.freeze()
456    }
457}
458
459impl Attributes {
460    pub fn encode(&self, asn_len: AsnLength) -> Bytes {
461        let mut bytes = BytesMut::new();
462        for attr in &self.inner {
463            bytes.extend(attr.encode(asn_len));
464        }
465        bytes.freeze()
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_unknwon_attribute_type() {
475        let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
476        let asn_len = AsnLength::Bits16;
477        let add_path = false;
478        let afi = None;
479        let safi = None;
480        let prefixes = None;
481        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
482        assert!(attributes.is_ok());
483        let attributes = attributes.unwrap();
484        assert_eq!(attributes.inner.len(), 1);
485        assert_eq!(
486            attributes.inner[0].value.attr_type(),
487            AttrType::Unknown(254)
488        );
489    }
490
491    #[test]
492    fn test_rfc7606_attribute_flags_error() {
493        // Create an ORIGIN attribute with wrong flags (should be transitive, not optional)
494        let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); // Optional flag set incorrectly
495        let asn_len = AsnLength::Bits16;
496        let add_path = false;
497        let afi = None;
498        let safi = None;
499        let prefixes = None;
500
501        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
502
503        // Should have validation warning for incorrect flags
504        assert!(attributes.has_validation_warnings());
505        let warnings = attributes.validation_warnings();
506        // Will have attribute flags error + missing mandatory attributes
507        assert!(!warnings.is_empty());
508
509        match &warnings[0] {
510            BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
511                assert_eq!(*attr_type, AttrType::ORIGIN);
512            }
513            _ => panic!("Expected AttributeFlagsError warning"),
514        }
515    }
516
517    #[test]
518    fn test_rfc7606_missing_mandatory_attribute() {
519        // Empty attributes - should have warnings for missing mandatory attributes
520        let data = Bytes::from(vec![]);
521        let asn_len = AsnLength::Bits16;
522        let add_path = false;
523        let afi = None;
524        let safi = None;
525        let prefixes = None;
526
527        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
528
529        // Should have warnings for missing mandatory attributes
530        assert!(attributes.has_validation_warnings());
531        let warnings = attributes.validation_warnings();
532        assert_eq!(warnings.len(), 3); // ORIGIN, AS_PATH, NEXT_HOP
533
534        for warning in warnings {
535            match warning {
536                BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
537                    assert!(matches!(
538                        attr_type,
539                        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
540                    ));
541                }
542                _ => panic!("Expected MissingWellKnownAttribute warning"),
543            }
544        }
545    }
546
547    #[test]
548    fn test_rfc7606_duplicate_attribute() {
549        // Create two ORIGIN attributes
550        let data = Bytes::from(vec![
551            0x40, 0x01, 0x01, 0x00, // First ORIGIN attribute
552            0x40, 0x01, 0x01, 0x01, // Second ORIGIN attribute (duplicate)
553        ]);
554        let asn_len = AsnLength::Bits16;
555        let add_path = false;
556        let afi = None;
557        let safi = None;
558        let prefixes = None;
559
560        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
561
562        // Should have warning for duplicate attribute
563        assert!(attributes.has_validation_warnings());
564        let warnings = attributes.validation_warnings();
565
566        // Should have at least one duplicate attribute warning
567        let has_duplicate_warning = warnings
568            .iter()
569            .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
570        assert!(has_duplicate_warning);
571    }
572
573    #[test]
574    fn test_attribute_type_boundaries() {
575        let asn_len = AsnLength::Bits16;
576        let add_path = false;
577        let afi = None;
578        let safi = None;
579        let prefixes = None;
580
581        // Required attributes for valid BGP message
582        const REQUIRED_ATTRS: &[u8] = &[
583            0x40, 0x01, 0x01, 0x00, // origin
584            0x40, 0x02, 0x00, // as_path
585            0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, // next_hop
586        ];
587
588        // Test highest (development) attribute type
589        let mut data = REQUIRED_ATTRS.to_vec();
590        data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); // development
591        let data = Bytes::from(data);
592
593        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
594
595        assert!(attributes.has_attr(AttrType::DEVELOPMENT));
596        assert!(!attributes.has_validation_warnings());
597
598        // Test lowest (reserved) attribute type
599        let mut data = REQUIRED_ATTRS.to_vec();
600        data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); // reserved
601        let data = Bytes::from(data);
602
603        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
604
605        // There is a validation warning about the reserved attribute
606        assert!(attributes.validation_warnings.iter().any(|vw| {
607            matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
608        }));
609    }
610
611    #[test]
612    fn test_rfc7606_attribute_length_error() {
613        // Create an ORIGIN attribute with wrong length (should be 1 byte, not 2)
614        let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
615        let asn_len = AsnLength::Bits16;
616        let add_path = false;
617        let afi = None;
618        let safi = None;
619        let prefixes = None;
620
621        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
622
623        // Should have warning for incorrect attribute length
624        assert!(attributes.has_validation_warnings());
625        let warnings = attributes.validation_warnings();
626
627        let has_length_warning = warnings
628            .iter()
629            .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
630        assert!(has_length_warning);
631    }
632
633    #[test]
634    fn test_rfc7606_no_session_reset() {
635        // Test that parsing continues even with multiple errors
636        let data = Bytes::from(vec![
637            0x80, 0x01, 0x02, 0x00, 0x01, // Wrong flags and length for ORIGIN
638            0x40, 0x01, 0x01, 0x00, // Duplicate ORIGIN
639            0x40, 0xFF, 0x01, 0x00, // Unknown attribute
640        ]);
641        let asn_len = AsnLength::Bits16;
642        let add_path = false;
643        let afi = None;
644        let safi = None;
645        let prefixes = None;
646
647        // Should not panic or return error - RFC 7606 requires continued parsing
648        let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
649        assert!(result.is_ok());
650
651        let attributes = result.unwrap();
652        assert!(attributes.has_validation_warnings());
653
654        // Should have multiple warnings but parsing should continue
655        let warnings = attributes.validation_warnings();
656        assert!(!warnings.is_empty());
657    }
658}