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 => parse_as_path(attr_data, asn_len).map(AttributeValue::AsPath),
417            AttrType::NEXT_HOP => parse_next_hop(attr_data, &afi),
418            AttrType::MULTI_EXIT_DISCRIMINATOR => parse_med(attr_data),
419            AttrType::LOCAL_PREFERENCE => parse_local_pref(attr_data),
420            AttrType::ATOMIC_AGGREGATE => Ok(AttributeValue::AtomicAggregate),
421            AttrType::AGGREGATOR => parse_aggregator(attr_data, asn_len)
422                .map(|(asn, id)| AttributeValue::Aggregator { asn, id }),
423            AttrType::ORIGINATOR_ID => parse_originator_id(attr_data),
424            AttrType::CLUSTER_LIST => parse_clusters(attr_data),
425            AttrType::MP_REACHABLE_NLRI => {
426                parse_nlri(attr_data, &afi, &safi, &prefixes, true, add_path)
427            }
428            AttrType::MP_UNREACHABLE_NLRI => {
429                parse_nlri(attr_data, &afi, &safi, &prefixes, false, add_path)
430            }
431            AttrType::AS4_PATH => {
432                parse_as_path(attr_data, &AsnLength::Bits32).map(AttributeValue::As4Path)
433            }
434            AttrType::AS4_AGGREGATOR => parse_aggregator(attr_data, &AsnLength::Bits32)
435                .map(|(asn, id)| AttributeValue::As4Aggregator { asn, id }),
436
437            // communities
438            AttrType::COMMUNITIES => parse_regular_communities(attr_data),
439            AttrType::LARGE_COMMUNITIES => parse_large_communities(attr_data),
440            AttrType::EXTENDED_COMMUNITIES => parse_extended_community(attr_data),
441            AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES => {
442                parse_ipv6_extended_community(attr_data)
443            }
444            AttrType::DEVELOPMENT => {
445                let mut value = vec![];
446                for _i in 0..attr_length {
447                    value.push(attr_data.read_u8()?);
448                }
449                Ok(AttributeValue::Development(value))
450            }
451            AttrType::ONLY_TO_CUSTOMER => parse_only_to_customer(attr_data),
452            AttrType::AIGP => parse_aigp(attr_data),
453            AttrType::TUNNEL_ENCAPSULATION => parse_tunnel_encapsulation_attribute(attr_data),
454            AttrType::TRAFFIC_ENGINEERING => parse_traffic_engineering(attr_data),
455            AttrType::BGP_LS_ATTRIBUTE => parse_link_state_attribute(attr_data),
456            AttrType::SFP_ATTRIBUTE => parse_sfp(attr_data),
457            AttrType::BFD_DISCRIMINATOR => parse_bfd_discriminator(attr_data),
458            AttrType::BGP_PREFIX_SID => parse_bgp_prefix_sid(attr_data),
459            AttrType::BIER => parse_bier(attr_data),
460            _ => Err(ParserError::Unsupported(format!(
461                "unsupported attribute type: {attr_type:?}"
462            ))),
463        };
464
465        match attr {
466            Ok(value) => {
467                assert_eq!(attr_type, value.attr_type());
468                attributes.push(Attribute { value, flag });
469            }
470            Err(e) => {
471                validation.observe_parse_error(attr_type, partial, &e);
472                attributes.push(Attribute {
473                    value: AttributeValue::Raw(AttrRaw {
474                        code: raw_code,
475                        bytes: raw_bytes,
476                    }),
477                    flag,
478                });
479                continue;
480            }
481        };
482    }
483
484    let (validation_warnings, attr_mask) = validation.finish();
485    Ok(Attributes {
486        inner: attributes,
487        validation_warnings,
488        attr_mask,
489    })
490}
491
492impl Attribute {
493    /// Append the wire representation of this attribute to `buf`.
494    ///
495    /// The length-field width follows the attribute's EXTENDED_LENGTH flag; a
496    /// value that does not fit the resulting field yields
497    /// [`EncodingError::ValueTooLarge`] instead of being truncated.
498    pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
499        buf.put_u8(self.flag.bits());
500        buf.put_u8(self.value.attr_code());
501
502        let write_value = |b: &mut BytesMut| -> Result<(), EncodingError> {
503            match &self.value {
504                AttributeValue::Origin(v) => b.put_slice(&encode_origin(v)),
505                // AS_PATH segment width follows the session's AS number length.
506                AttributeValue::AsPath(path) => {
507                    encode_as_path(path, asn_len, b)?;
508                }
509                // AS4_PATH segments are 4-octet by definition (RFC 6793 ยง4.2),
510                // independent of the session's AS number length.
511                AttributeValue::As4Path(path) => {
512                    encode_as_path(path, AsnLength::Bits32, b)?;
513                }
514                AttributeValue::NextHop(v) => b.put_slice(&encode_next_hop(v)),
515                AttributeValue::MultiExitDiscriminator(v) => b.put_slice(&encode_med(*v)),
516                AttributeValue::LocalPreference(v) => b.put_slice(&encode_local_pref(*v)),
517                AttributeValue::OnlyToCustomer(v) => {
518                    b.put_slice(&encode_only_to_customer(v.into()))
519                }
520                AttributeValue::AtomicAggregate => {}
521                AttributeValue::Aggregator { asn, id } => {
522                    b.put_slice(&encode_aggregator(asn, &IpAddr::from(*id), asn_len)?)
523                }
524                AttributeValue::As4Aggregator { asn, id } => b.put_slice(&encode_aggregator(
525                    asn,
526                    &IpAddr::from(*id),
527                    AsnLength::Bits32,
528                )?),
529                AttributeValue::Communities(v) => b.put_slice(&encode_regular_communities(v)),
530                AttributeValue::ExtendedCommunities(v) => {
531                    b.put_slice(&encode_extended_communities(v))
532                }
533                AttributeValue::LargeCommunities(v) => b.put_slice(&encode_large_communities(v)),
534                AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => {
535                    b.put_slice(&encode_ipv6_extended_communities(v))
536                }
537                AttributeValue::OriginatorId(v) => {
538                    b.put_slice(&encode_originator_id(&IpAddr::from(*v)))
539                }
540                AttributeValue::Clusters(v) => b.put_slice(&encode_clusters(v)),
541                AttributeValue::MpReachNlri(v) => {
542                    // Infer ADD-PATH from presence of path_id in any labeled prefix
543                    let add_path = v
544                        .labeled_prefixes
545                        .as_ref()
546                        .is_some_and(|prefixes| prefixes.iter().any(|p| p.path_id.is_some()));
547                    encode_nlri(v, true, add_path, b)?;
548                }
549                AttributeValue::MpUnreachNlri(v) => {
550                    // Withdrawals don't use ADD-PATH encoding per RFC 8277
551                    encode_nlri(v, false, false, b)?;
552                }
553                AttributeValue::LinkState(v) => encode_link_state_attribute(v, b)?,
554                AttributeValue::TunnelEncapsulation(v) => {
555                    encode_tunnel_encapsulation_attribute(v, b)?
556                }
557                AttributeValue::TrafficEngineering(v) => encode_traffic_engineering(v, b)?,
558                AttributeValue::BfdDiscriminator(v) => encode_bfd_discriminator(v, b)?,
559                AttributeValue::BgpPrefixSid(v) => encode_bgp_prefix_sid(v, b)?,
560                AttributeValue::Bier(v) => encode_bier(v, b)?,
561                AttributeValue::Sfp(v) => encode_sfp(v, b)?,
562                AttributeValue::Development(v) => b.put_slice(v),
563                AttributeValue::Raw(v) => b.put_slice(&v.bytes),
564                AttributeValue::Deprecated(v) => b.put_slice(&v.bytes),
565                AttributeValue::Unknown(v) => b.put_slice(&v.bytes),
566                AttributeValue::Aigp(v) => b.put_slice(&encode_aigp(v)),
567                AttributeValue::AttrSet(_v) => {
568                    return Err(EncodingError::unencodable(
569                        "ATTR_SET attribute",
570                        "encoding not implemented",
571                    ));
572                }
573            }
574            Ok(())
575        };
576
577        match self.is_extended() {
578            false => with_u8_len(buf, "BGP attribute value length", write_value),
579            true => with_u16_len(buf, "BGP attribute value length (extended)", write_value),
580        }
581    }
582
583    /// Encode this attribute into a fresh buffer.
584    pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
585        let mut buf = BytesMut::new();
586        self.encode_to(asn_len, &mut buf)?;
587        Ok(buf.freeze())
588    }
589}
590
591impl Attributes {
592    /// Append the wire representation of all attributes to `buf`.
593    pub fn encode_to(&self, asn_len: AsnLength, buf: &mut BytesMut) -> Result<(), EncodingError> {
594        for attr in &self.inner {
595            attr.encode_to(asn_len, buf)?;
596        }
597        Ok(())
598    }
599
600    /// Encode all attributes into a fresh buffer.
601    pub fn encode(&self, asn_len: AsnLength) -> Result<Bytes, EncodingError> {
602        let mut buf = BytesMut::new();
603        self.encode_to(asn_len, &mut buf)?;
604        Ok(buf.freeze())
605    }
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611
612    #[test]
613    fn test_unknwon_attribute_type() {
614        let data = Bytes::from(vec![0x40, 0xFE, 0x00]);
615        let asn_len = AsnLength::Bits16;
616        let add_path = false;
617        let afi = None;
618        let safi = None;
619        let prefixes = None;
620        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
621        assert!(attributes.is_ok());
622        let attributes = attributes.unwrap();
623        assert_eq!(attributes.inner.len(), 1);
624        assert_eq!(
625            attributes.inner[0].value.attr_type(),
626            AttrType::Unknown(254)
627        );
628    }
629
630    #[test]
631    fn test_rfc7606_attribute_flags_error() {
632        // Create an ORIGIN attribute with wrong flags (should be transitive, not optional)
633        let data = Bytes::from(vec![0x80, 0x01, 0x01, 0x00]); // Optional flag set incorrectly
634        let asn_len = AsnLength::Bits16;
635        let add_path = false;
636        let afi = None;
637        let safi = None;
638        let prefixes = None;
639
640        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
641
642        // Should have validation warning for incorrect flags
643        assert!(attributes.has_validation_warnings());
644        let warnings = attributes.validation_warnings();
645        // Will have attribute flags error + missing mandatory attributes
646        assert!(!warnings.is_empty());
647
648        match &warnings[0] {
649            BgpValidationWarning::AttributeFlagsError { attr_type, .. } => {
650                assert_eq!(*attr_type, AttrType::ORIGIN);
651            }
652            _ => panic!("Expected AttributeFlagsError warning"),
653        }
654    }
655
656    #[test]
657    fn test_rfc7606_missing_mandatory_attribute() {
658        // Attributes with only LOCAL_PREF (missing ORIGIN, AS_PATH, NEXT_HOP)
659        let data = Bytes::from(vec![
660            0x40, 0x05, 0x04, 0x00, 0x00, 0x00, 0x64, // LOCAL_PREF = 100
661        ]);
662        let asn_len = AsnLength::Bits16;
663        let add_path = false;
664        let afi = None;
665        let safi = None;
666        let prefixes = None;
667
668        let mut attributes =
669            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
670        // Manually trigger mandatory check as an announcement with standard NLRI
671        attributes.check_mandatory_attributes(true, true);
672
673        // Should have warnings for missing mandatory attributes
674        assert!(attributes.has_validation_warnings());
675        let warnings = attributes.validation_warnings();
676        // LOCAL_PREF is not a withdrawal, so ORIGIN, AS_PATH, NEXT_HOP are required
677        assert_eq!(warnings.len(), 3); // ORIGIN, AS_PATH, NEXT_HOP
678
679        for warning in warnings {
680            match warning {
681                BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
682                    assert!(matches!(
683                        attr_type,
684                        AttrType::ORIGIN | AttrType::AS_PATH | AttrType::NEXT_HOP
685                    ));
686                }
687                _ => panic!("Expected MissingWellKnownAttribute warning"),
688            }
689        }
690    }
691
692    #[test]
693    fn test_mp_reach_no_next_hop() {
694        // Attributes with MP_REACH_NLRI (missing ORIGIN, AS_PATH)
695        // MP_REACH_NLRI is type 14 (0x0E).
696        // We just need a dummy MP_REACH_NLRI.
697        let data = Bytes::from(vec![
698            0x80, 0x0E, 0x06, 0x00, 0x01, 0x01, // AFI=1, SAFI=1
699            0x00, // Next Hop Len = 0 (invalid for parsing, but enough to trigger logic)
700            0x00, // Reserved
701            0x00, // NLRI
702        ]);
703        let asn_len = AsnLength::Bits16;
704        let add_path = false;
705        let afi = None;
706        let safi = None;
707        let prefixes = None;
708
709        let mut attributes =
710            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
711        // Manually trigger mandatory check as an announcement but NO standard NLRI (MP only)
712        attributes.check_mandatory_attributes(true, false);
713
714        // Should NOT have NEXT_HOP warning because MP_REACH_NLRI is present and has_standard_nlri is false
715        let warnings = attributes.validation_warnings();
716        let has_next_hop_warning = warnings.iter().any(|w| {
717            matches!(
718                w,
719                BgpValidationWarning::MissingWellKnownAttribute {
720                    attr_type: AttrType::NEXT_HOP
721                }
722            )
723        });
724        assert!(!has_next_hop_warning);
725    }
726
727    #[test]
728    fn test_pure_withdrawal_no_warnings() {
729        // Empty attributes - pure withdrawal
730        let data = Bytes::from(vec![]);
731        let asn_len = AsnLength::Bits16;
732        let add_path = false;
733        let afi = None;
734        let safi = None;
735        let prefixes = None;
736
737        let mut attributes =
738            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
739        // Manually trigger mandatory check as a withdrawal
740        attributes.check_mandatory_attributes(false, false);
741
742        // Should have NO warnings
743        assert!(!attributes.has_validation_warnings());
744
745        // Attributes with only MP_UNREACH_NLRI - pure withdrawal
746        let data = Bytes::from(vec![
747            0x80, 0x0F, 0x03, 0x00, 0x01, 0x01, // AFI=1, SAFI=1
748        ]);
749        let mut attributes =
750            parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
751        attributes.check_mandatory_attributes(false, false);
752        assert!(!attributes.has_validation_warnings());
753    }
754
755    #[test]
756    fn test_rfc7606_duplicate_attribute() {
757        // Create two ORIGIN attributes
758        let data = Bytes::from(vec![
759            0x40, 0x01, 0x01, 0x00, // First ORIGIN attribute
760            0x40, 0x01, 0x01, 0x01, // Second ORIGIN attribute (duplicate)
761        ]);
762        let asn_len = AsnLength::Bits16;
763        let add_path = false;
764        let afi = None;
765        let safi = None;
766        let prefixes = None;
767
768        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
769
770        // Should have warning for duplicate attribute
771        assert!(attributes.has_validation_warnings());
772        let warnings = attributes.validation_warnings();
773
774        // Should have at least one duplicate attribute warning
775        let has_duplicate_warning = warnings
776            .iter()
777            .any(|w| matches!(w, BgpValidationWarning::DuplicateAttribute { .. }));
778        assert!(has_duplicate_warning);
779    }
780
781    #[test]
782    fn test_attribute_type_boundaries() {
783        let asn_len = AsnLength::Bits16;
784        let add_path = false;
785        let afi = None;
786        let safi = None;
787        let prefixes = None;
788
789        // Required attributes for valid BGP message
790        const REQUIRED_ATTRS: &[u8] = &[
791            0x40, 0x01, 0x01, 0x00, // origin
792            0x40, 0x02, 0x00, // as_path
793            0x40, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, // next_hop
794        ];
795
796        // Test highest (development) attribute type
797        let mut data = REQUIRED_ATTRS.to_vec();
798        data.extend_from_slice(&[0x40, 0xFF, 0x01, 0x00]); // development
799        let data = Bytes::from(data);
800
801        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
802
803        assert!(attributes.has_attr(AttrType::DEVELOPMENT));
804        assert!(!attributes.has_validation_warnings());
805
806        // Test lowest (reserved) attribute type
807        let mut data = REQUIRED_ATTRS.to_vec();
808        data.extend_from_slice(&[0x40, 0x00, 0x01, 0x01]); // reserved
809        let data = Bytes::from(data);
810
811        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
812
813        assert!(attributes.inner.iter().any(|attr| {
814            matches!(
815                &attr.value,
816                AttributeValue::Raw(raw)
817                    if raw.code == u8::from(AttrType::RESERVED)
818                        && raw.bytes == Bytes::from_static(&[0x01])
819            )
820        }));
821        assert!(!attributes.validation_warnings.iter().any(|vw| {
822            matches!(vw, BgpValidationWarning::OptionalAttributeError { attr_type, reason:_ } if *attr_type == AttrType::RESERVED)
823        }));
824    }
825
826    #[test]
827    fn test_raw_retention_for_known_unsupported_attribute() {
828        let data = Bytes::from(vec![0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc]); // PMSI_TUNNEL
829        let attributes =
830            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
831
832        assert_eq!(attributes.inner.len(), 1);
833        match &attributes.inner[0].value {
834            AttributeValue::Raw(raw) => {
835                assert_eq!(raw.code, 22);
836                assert_eq!(raw.attr_type(), AttrType::PMSI_TUNNEL);
837                assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb, 0xcc]));
838            }
839            value => panic!("expected Raw, got {value:?}"),
840        }
841        assert_eq!(
842            attributes.encode(AsnLength::Bits16).unwrap(),
843            Bytes::from_static(&[0x80, 0x16, 0x03, 0xaa, 0xbb, 0xcc])
844        );
845    }
846
847    #[test]
848    fn test_deprecated_code_13_retained_as_deprecated() {
849        let data = Bytes::from(vec![0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04]);
850        let attributes =
851            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
852
853        assert_eq!(attributes.inner.len(), 1);
854        match &attributes.inner[0].value {
855            AttributeValue::Deprecated(raw) => {
856                assert_eq!(raw.code, 13);
857                assert_eq!(raw.attr_type(), AttrType::Unknown(13));
858                assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03, 0x04]));
859            }
860            value => panic!("expected Deprecated, got {value:?}"),
861        }
862        assert_eq!(
863            attributes.encode(AsnLength::Bits16).unwrap(),
864            Bytes::from_static(&[0x80, 0x0d, 0x04, 0x01, 0x02, 0x03, 0x04])
865        );
866    }
867
868    #[test]
869    fn test_malformed_typed_attribute_falls_back_to_raw() {
870        let data = Bytes::from(vec![0x40, 0x03, 0x03, 0x01, 0x02, 0x03]); // NEXT_HOP length must be 4
871        let attributes =
872            parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
873
874        assert!(attributes.has_validation_warnings());
875        match &attributes.inner[0].value {
876            AttributeValue::Raw(raw) => {
877                assert_eq!(raw.code, 3);
878                assert_eq!(raw.attr_type(), AttrType::NEXT_HOP);
879                assert_eq!(raw.bytes, Bytes::from_static(&[0x01, 0x02, 0x03]));
880            }
881            value => panic!("expected Raw fallback, got {value:?}"),
882        }
883        assert_eq!(
884            attributes.encode(AsnLength::Bits16).unwrap(),
885            Bytes::from_static(&[0x40, 0x03, 0x03, 0x01, 0x02, 0x03])
886        );
887    }
888
889    #[test]
890    fn test_all_raw_retained_attribute_codes_parse_and_round_trip() {
891        let raw_codes = [0, 22, 24, 27, 33, 128];
892
893        for code in raw_codes {
894            let wire = vec![0xc0, code, 0x02, 0xaa, 0xbb];
895            let attributes = parse_attributes(
896                Bytes::from(wire.clone()),
897                &AsnLength::Bits16,
898                false,
899                None,
900                None,
901                None,
902            )
903            .unwrap();
904            assert_eq!(attributes.inner.len(), 1, "code {code}");
905            match &attributes.inner[0].value {
906                AttributeValue::Raw(raw) => {
907                    assert_eq!(raw.code, code);
908                    assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
909                }
910                value => panic!("expected Raw for code {code}, got {value:?}"),
911            }
912            assert!(attributes.has_attr(AttrType::from(code)), "code {code}");
913            assert_eq!(
914                attributes.encode(AsnLength::Bits16).unwrap(),
915                Bytes::from(wire)
916            );
917        }
918    }
919
920    #[test]
921    fn test_unassigned_attribute_code_retained_as_unknown() {
922        let wire = vec![0xc0, 0x7f, 0x02, 0xaa, 0xbb];
923        let attributes = parse_attributes(
924            Bytes::from(wire.clone()),
925            &AsnLength::Bits16,
926            false,
927            None,
928            None,
929            None,
930        )
931        .unwrap();
932
933        assert_eq!(attributes.inner.len(), 1);
934        match &attributes.inner[0].value {
935            AttributeValue::Unknown(raw) => {
936                assert_eq!(raw.code, 0x7f);
937                assert_eq!(raw.attr_type(), AttrType::Unknown(0x7f));
938                assert_eq!(raw.bytes, Bytes::from_static(&[0xaa, 0xbb]));
939            }
940            value => panic!("expected Unknown, got {value:?}"),
941        }
942        assert!(attributes.has_attr(AttrType::Unknown(0x7f)));
943        assert_eq!(
944            attributes.encode(AsnLength::Bits16).unwrap(),
945            Bytes::from(wire)
946        );
947    }
948
949    #[test]
950    fn test_structured_tlv_attributes_parse_and_round_trip() {
951        let cases = [
952            (
953                vec![0xc0, 0x26, 0x05, 0x01, 0x01, 0x02, 0x03, 0x04],
954                "BFD Discriminator",
955            ),
956            (
957                vec![0xc0, 0x28, 0x05, 0x7f, 0x00, 0x02, 0xaa, 0xbb],
958                "BGP Prefix-SID",
959            ),
960            (
961                vec![0xc0, 0x29, 0x06, 0x12, 0x34, 0x00, 0x02, 0xde, 0xad],
962                "BIER",
963            ),
964            (vec![0xc0, 0x25, 0x05, 0x7f, 0x00, 0x02, 0xde, 0xad], "SFP"),
965        ];
966
967        for (wire, name) in cases {
968            let data = Bytes::from(wire.clone());
969            let attributes =
970                parse_attributes(data, &AsnLength::Bits16, false, None, None, None).unwrap();
971            assert_eq!(attributes.inner.len(), 1, "{name}");
972            match (name, &attributes.inner[0].value) {
973                ("BFD Discriminator", AttributeValue::BfdDiscriminator(_))
974                | ("BGP Prefix-SID", AttributeValue::BgpPrefixSid(_))
975                | ("BIER", AttributeValue::Bier(_))
976                | ("SFP", AttributeValue::Sfp(_)) => {}
977                (_, value) => panic!("unexpected value for {name}: {value:?}"),
978            }
979            assert_eq!(
980                attributes.encode(AsnLength::Bits16).unwrap(),
981                Bytes::from(wire),
982                "{name}"
983            );
984        }
985    }
986
987    #[test]
988    fn test_rfc7606_attribute_length_error() {
989        // Create an ORIGIN attribute with wrong length (should be 1 byte, not 2)
990        let data = Bytes::from(vec![0x40, 0x01, 0x02, 0x00, 0x01]);
991        let asn_len = AsnLength::Bits16;
992        let add_path = false;
993        let afi = None;
994        let safi = None;
995        let prefixes = None;
996
997        let attributes = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes).unwrap();
998
999        // Should have warning for incorrect attribute length
1000        assert!(attributes.has_validation_warnings());
1001        let warnings = attributes.validation_warnings();
1002
1003        let has_length_warning = warnings
1004            .iter()
1005            .any(|w| matches!(w, BgpValidationWarning::AttributeLengthError { .. }));
1006        assert!(has_length_warning);
1007    }
1008
1009    #[test]
1010    fn test_rfc7606_no_session_reset() {
1011        // Test that parsing continues even with multiple errors
1012        let data = Bytes::from(vec![
1013            0x80, 0x01, 0x02, 0x00, 0x01, // Wrong flags and length for ORIGIN
1014            0x40, 0x01, 0x01, 0x00, // Duplicate ORIGIN
1015            0x40, 0xFF, 0x01, 0x00, // Unknown attribute
1016        ]);
1017        let asn_len = AsnLength::Bits16;
1018        let add_path = false;
1019        let afi = None;
1020        let safi = None;
1021        let prefixes = None;
1022
1023        // Should not panic or return error - RFC 7606 requires continued parsing
1024        let result = parse_attributes(data, &asn_len, add_path, afi, safi, prefixes);
1025        assert!(result.is_ok());
1026
1027        let attributes = result.unwrap();
1028        assert!(attributes.has_validation_warnings());
1029
1030        // Should have multiple warnings but parsing should continue
1031        let warnings = attributes.validation_warnings();
1032        assert!(!warnings.is_empty());
1033    }
1034
1035    #[test]
1036    fn test_encode_as_path_attribute_type_selection() {
1037        // Regression test for issue #329: an AS_PATH built for a 4-octet
1038        // session must encode as attribute type 2 with 4-octet segments, not
1039        // as AS4_PATH (type 17).
1040        let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1041            400644,
1042        ]))]);
1043        let buf = attrs.encode(AsnLength::Bits32).unwrap();
1044        assert_eq!(buf[1], 2, "attribute type must be AS_PATH (2)");
1045        // segment header (type 2, count 1) + one 4-octet ASN
1046        assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1047
1048        // AS4_PATH always encodes as type 17 with 4-octet segments, even on a
1049        // 2-octet session (RFC 6793 ยง4.2).
1050        let attrs = Attributes::from_iter(vec![AttributeValue::As4Path(AsPath::from_sequence([
1051            400644,
1052        ]))]);
1053        let buf = attrs.encode(AsnLength::Bits16).unwrap();
1054        assert_eq!(buf[1], 17, "attribute type must be AS4_PATH (17)");
1055        assert_eq!(&buf[3..], &[0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1056    }
1057
1058    #[test]
1059    fn test_encode_as_path_on_2octet_session() {
1060        let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1061            64496, 64497,
1062        ]))]);
1063        let buf = attrs.encode(AsnLength::Bits16).unwrap();
1064        assert_eq!(buf[1], 2);
1065        // segment header (type 2, count 2) + two 2-octet ASNs
1066        assert_eq!(&buf[3..], &[0x02, 0x02, 0xFB, 0xF0, 0xFB, 0xF1]);
1067    }
1068
1069    #[test]
1070    fn test_encode_as_path_rejects_4octet_asn_on_2octet_session() {
1071        // A 4-octet AS number that does not fit a 2-octet segment must be an
1072        // encoding error, not a silent truncation to its low 16 bits.
1073        let attrs = Attributes::from_iter(vec![AttributeValue::AsPath(AsPath::from_sequence([
1074            400644,
1075        ]))]);
1076        let err = attrs.encode(AsnLength::Bits16).unwrap_err();
1077        assert_eq!(
1078            err,
1079            EncodingError::ValueTooLarge {
1080                field: "2-octet AS number in AS_PATH segment",
1081                actual: 400644,
1082                max: 65535,
1083            }
1084        );
1085    }
1086
1087    #[test]
1088    fn test_encode_aggregator_attribute_type_selection() {
1089        let attrs = Attributes::from_iter(vec![AttributeValue::As4Aggregator {
1090            asn: Asn::new_32bit(400644),
1091            id: std::net::Ipv4Addr::new(192, 0, 2, 1),
1092        }]);
1093        let buf = attrs.encode(AsnLength::Bits16).unwrap();
1094        assert_eq!(buf[1], 18, "attribute type must be AS4_AGGREGATOR (18)");
1095        assert_eq!(
1096            buf.len(),
1097            3 + 8,
1098            "value must be a 4-octet ASN plus an IPv4 id"
1099        );
1100    }
1101
1102    #[test]
1103    fn test_encode_aggregator_width_follows_session_not_asn_flag() {
1104        // A 4-octet-flagged Asn holding a value that fits 16 bits must encode
1105        // as 2 octets on a 2-octet session: the session width decides, not the
1106        // Asn's internal flag.
1107        let attrs = Attributes::from_iter(vec![AttributeValue::Aggregator {
1108            asn: Asn::new_32bit(258),
1109            id: std::net::Ipv4Addr::new(10, 0, 0, 1),
1110        }]);
1111        let buf = attrs.encode(AsnLength::Bits16).unwrap();
1112        assert_eq!(buf[1], 7, "attribute type must be AGGREGATOR (7)");
1113        assert_eq!(buf.len(), 3 + 6);
1114        assert_eq!(&buf[3..], &[0x01, 0x02, 10, 0, 0, 1]);
1115    }
1116
1117    #[test]
1118    fn test_parse_attributes_maps_as4_wire_types() {
1119        // AS4_PATH (type 17): optional transitive flag 0xC0, one AS_SEQUENCE
1120        // segment with the 4-octet ASN 400644.
1121        let data = Bytes::from(vec![0xC0, 17, 6, 0x02, 0x01, 0x00, 0x06, 0x1D, 0x04]);
1122        let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1123            .expect("AS4_PATH must parse");
1124        match attributes.get_attr(AttrType::AS4_PATH).unwrap().value {
1125            AttributeValue::As4Path(path) => assert_eq!(
1126                path.to_u32_vec_opt(false).unwrap(),
1127                vec![400644],
1128                "AS4_PATH segments must parse as 4-octet regardless of session length"
1129            ),
1130            other => panic!("expected As4Path variant, got {other:?}"),
1131        }
1132
1133        // AS4_AGGREGATOR (type 18): 4-octet ASN plus an IPv4 id.
1134        let data = Bytes::from(vec![0xC0, 18, 8, 0x00, 0x06, 0x1D, 0x04, 192, 0, 2, 1]);
1135        let attributes = parse_attributes(data, &AsnLength::Bits16, false, None, None, None)
1136            .expect("AS4_AGGREGATOR must parse");
1137        match attributes.get_attr(AttrType::AS4_AGGREGATOR).unwrap().value {
1138            AttributeValue::As4Aggregator { asn, .. } => {
1139                assert_eq!(asn, Asn::new_32bit(400644))
1140            }
1141            other => panic!("expected As4Aggregator variant, got {other:?}"),
1142        }
1143    }
1144}