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