Skip to main content

bgpkit_parser/models/bgp/attributes/
mod.rs

1//! BGP attribute structs
2mod aspath;
3mod nlri;
4mod origin;
5
6use crate::models::network::*;
7use bitflags::bitflags;
8use bytes::Bytes;
9use num_enum::{FromPrimitive, IntoPrimitive};
10use std::cmp::Ordering;
11use std::iter::{FromIterator, Map};
12use std::net::IpAddr;
13use std::slice::Iter;
14use std::vec::IntoIter;
15
16use crate::error::BgpValidationWarning;
17use crate::models::*;
18
19pub use aspath::*;
20pub use nlri::*;
21pub use origin::*;
22
23bitflags! {
24    /// The high-order bit (bit 0) of the Attribute Flags octet is the
25    /// Optional bit.  It defines whether the attribute is optional (if
26    /// set to 1) or well-known (if set to 0).
27    ///
28    /// The second high-order bit (bit 1) of the Attribute Flags octet
29    /// is the Transitive bit.  It defines whether an optional
30    /// attribute is transitive (if set to 1) or non-transitive (if set
31    /// to 0).
32    ///
33    /// For well-known attributes, the Transitive bit MUST be set to 1.
34    /// (See Section 5 for a discussion of transitive attributes.)
35    ///
36    /// The third high-order bit (bit 2) of the Attribute Flags octet
37    /// is the Partial bit.  It defines whether the information
38    /// contained in the optional transitive attribute is partial (if
39    /// set to 1) or complete (if set to 0).  For well-known attributes
40    /// and for optional non-transitive attributes, the Partial bit
41    /// MUST be set to 0.
42    ///
43    /// The fourth high-order bit (bit 3) of the Attribute Flags octet
44    /// is the Extended Length bit.  It defines whether the Attribute
45    /// Length is one octet (if set to 0) or two octets (if set to 1).
46    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48    pub struct AttrFlags: u8 {
49        const OPTIONAL   = 0b10000000;
50        const TRANSITIVE = 0b01000000;
51        const PARTIAL    = 0b00100000;
52        const EXTENDED   = 0b00010000;
53    }
54}
55
56/// Attribute types.
57///
58/// All attributes currently defined and not Unassigned or Deprecated are included here.
59/// To see the full list, check out IANA at:
60/// <https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2>
61#[allow(non_camel_case_types)]
62#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, FromPrimitive, IntoPrimitive)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[repr(u8)]
65pub enum AttrType {
66    RESERVED = 0,
67    ORIGIN = 1,
68    AS_PATH = 2,
69    NEXT_HOP = 3,
70    MULTI_EXIT_DISCRIMINATOR = 4,
71    LOCAL_PREFERENCE = 5,
72    ATOMIC_AGGREGATE = 6,
73    AGGREGATOR = 7,
74    COMMUNITIES = 8,
75    /// <https://tools.ietf.org/html/rfc4456>
76    ORIGINATOR_ID = 9,
77    CLUSTER_LIST = 10,
78    /// <https://tools.ietf.org/html/rfc4760>
79    MP_REACHABLE_NLRI = 14,
80    MP_UNREACHABLE_NLRI = 15,
81    /// <https://datatracker.ietf.org/doc/html/rfc4360>
82    EXTENDED_COMMUNITIES = 16,
83    AS4_PATH = 17,
84    AS4_AGGREGATOR = 18,
85    PMSI_TUNNEL = 22,
86    TUNNEL_ENCAPSULATION = 23,
87    TRAFFIC_ENGINEERING = 24,
88    IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
89    AIGP = 26,
90    PE_DISTINGUISHER_LABELS = 27,
91    BGP_LS_ATTRIBUTE = 29,
92    LARGE_COMMUNITIES = 32,
93    BGPSEC_PATH = 33,
94    ONLY_TO_CUSTOMER = 35,
95    SFP_ATTRIBUTE = 37,
96    BFD_DISCRIMINATOR = 38,
97    BGP_PREFIX_SID = 40,
98    BIER = 41,
99    ATTR_SET = 128,
100    /// <https://datatracker.ietf.org/doc/html/rfc2042>
101    DEVELOPMENT = 255,
102
103    /// Catch all for any unknown attribute types
104    #[num_enum(catch_all)]
105    // We have to explicitly assign this variant a number, otherwise the compiler will attempt to
106    // assign it to 256 (previous + 1) and overflow the type.
107    Unknown(u8) = 254,
108}
109
110impl PartialOrd for AttrType {
111    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116impl Ord for AttrType {
117    fn cmp(&self, other: &Self) -> Ordering {
118        u8::from(*self).cmp(&u8::from(*other))
119    }
120}
121
122pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
123    match attr_type {
124        11 => Some("DPA"),
125        12 => Some("ADVERTISER"),
126        13 => Some("RCID_PATH"),
127        19 => Some("SAFI Specific Attribute"),
128        20 => Some("Connector Attribute"),
129        21 => Some("AS_PATHLIMIT"),
130        28 => Some("BGP Entropy Label Capability"),
131        30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
132
133        _ => None,
134    }
135}
136
137/// Convenience wrapper for a list of attributes
138#[derive(PartialEq, Clone, Default, Eq)]
139pub struct Attributes {
140    // Black box type to allow for later changes/optimizations. The most common attributes could be
141    // added as fields to allow for easier lookup.
142    pub(crate) inner: Vec<Attribute>,
143    /// RFC 7606 validation warnings collected during parsing
144    pub(crate) validation_warnings: Vec<BgpValidationWarning>,
145    /// Bitmask of seen attributes to allow O(1) checks. Fits in 4 u64s.
146    pub(crate) attr_mask: [u64; 4],
147}
148
149impl std::fmt::Debug for Attributes {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        f.debug_struct("Attributes")
152            .field("inner", &self.inner)
153            .field("validation_warnings", &self.validation_warnings)
154            .finish()
155    }
156}
157
158impl Attributes {
159    pub fn has_attr(&self, ty: AttrType) -> bool {
160        let attr = u8::from(ty);
161        (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
162    }
163
164    pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
165        self.inner
166            .iter()
167            .find(|x| x.value.attr_type() == ty)
168            .cloned()
169    }
170
171    pub fn add_attr(&mut self, attr: Attribute) {
172        let ty = attr.value.attr_code();
173        self.attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
174        self.inner.push(attr);
175    }
176
177    /// Check for missing well-known mandatory attributes.
178    ///
179    /// RFC 4271 (BGP-4) and RFC 4760 (MP-BGP) define which attributes are mandatory.
180    /// - Pure Withdrawals: NO path attributes are required.
181    /// - Announcements: ORIGIN and AS_PATH are strictly required.
182    /// - NEXT_HOP is required if standard IPv4 NLRI is present or if no MP_REACH_NLRI is present.
183    pub fn check_mandatory_attributes(&mut self, is_announcement: bool, has_standard_nlri: bool) {
184        if !is_announcement {
185            return;
186        }
187
188        // ORIGIN and AS_PATH are universally mandatory for all announcements.
189        if !self.has_attr(AttrType::ORIGIN) {
190            self.validation_warnings
191                .push(BgpValidationWarning::MissingWellKnownAttribute {
192                    attr_type: AttrType::ORIGIN,
193                });
194        }
195        if !self.has_attr(AttrType::AS_PATH) {
196            self.validation_warnings
197                .push(BgpValidationWarning::MissingWellKnownAttribute {
198                    attr_type: AttrType::AS_PATH,
199                });
200        }
201
202        // NEXT_HOP is required if this is an IPv4 announcement (has standard NLRI)
203        // or if we haven't seen MP_REACH_NLRI,
204        // which implies standard NLRI is expected, since is_announcement.
205        let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
206        if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
207            self.validation_warnings
208                .push(BgpValidationWarning::MissingWellKnownAttribute {
209                    attr_type: AttrType::NEXT_HOP,
210                });
211        }
212    }
213
214    /// Add a validation warning to the attributes
215    pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
216        self.validation_warnings.push(warning);
217    }
218
219    /// Get all validation warnings for these attributes
220    pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
221        &self.validation_warnings
222    }
223
224    /// Check if there are any validation warnings
225    pub fn has_validation_warnings(&self) -> bool {
226        !self.validation_warnings.is_empty()
227    }
228
229    /// Get the `ORIGIN` attribute. In the event that this attribute is not present,
230    /// [Origin::INCOMPLETE] will be returned instead.
231    pub fn origin(&self) -> Origin {
232        self.inner
233            .iter()
234            .find_map(|x| match &x.value {
235                AttributeValue::Origin(x) => Some(*x),
236                _ => None,
237            })
238            .unwrap_or(Origin::INCOMPLETE)
239    }
240
241    /// Get the `ORIGINATOR_ID` attribute if present.
242    pub fn origin_id(&self) -> Option<BgpIdentifier> {
243        self.inner.iter().find_map(|x| match &x.value {
244            AttributeValue::OriginatorId(x) => Some(*x),
245            _ => None,
246        })
247    }
248
249    /// Get the `NEXT_HOP` attribute if present.
250    ///
251    /// **Note**: Even when this attribute is not present, the next hop address may still be
252    /// attainable from the `MP_REACH_NLRI` attribute.
253    pub fn next_hop(&self) -> Option<IpAddr> {
254        self.inner.iter().find_map(|x| match &x.value {
255            AttributeValue::NextHop(x) => Some(*x),
256            _ => None,
257        })
258    }
259
260    pub fn multi_exit_discriminator(&self) -> Option<u32> {
261        self.inner.iter().find_map(|x| match &x.value {
262            AttributeValue::MultiExitDiscriminator(x) => Some(*x),
263            _ => None,
264        })
265    }
266
267    pub fn local_preference(&self) -> Option<u32> {
268        self.inner.iter().find_map(|x| match &x.value {
269            AttributeValue::LocalPreference(x) => Some(*x),
270            _ => None,
271        })
272    }
273
274    pub fn only_to_customer(&self) -> Option<Asn> {
275        self.inner.iter().find_map(|x| match &x.value {
276            AttributeValue::OnlyToCustomer(x) => Some(*x),
277            _ => None,
278        })
279    }
280
281    pub fn atomic_aggregate(&self) -> bool {
282        self.inner
283            .iter()
284            .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
285    }
286
287    pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
288        // Begin searching at the end of the attributes to increase the odds of finding an AS4
289        // attribute first.
290        self.inner.iter().rev().find_map(|x| match &x.value {
291            AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
292            _ => None,
293        })
294    }
295
296    pub fn clusters(&self) -> Option<&[u32]> {
297        self.inner.iter().find_map(|x| match &x.value {
298            AttributeValue::Clusters(x) => Some(x.as_ref()),
299            _ => None,
300        })
301    }
302
303    // These implementations are horribly inefficient, but they were super easy to write and use
304    pub fn as_path(&self) -> Option<&AsPath> {
305        // Begin searching at the end of the attributes to increase the odds of finding an AS4
306        // attribute first.
307        self.inner.iter().rev().find_map(|x| match &x.value {
308            AttributeValue::AsPath { path, .. } => Some(path),
309            _ => None,
310        })
311    }
312
313    pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
314        self.inner.iter().find_map(|x| match &x.value {
315            AttributeValue::MpReachNlri(x) => Some(x),
316            _ => None,
317        })
318    }
319
320    pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
321        self.inner.iter().find_map(|x| match &x.value {
322            AttributeValue::MpUnreachNlri(x) => Some(x),
323            _ => None,
324        })
325    }
326
327    pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
328        MetaCommunitiesIter {
329            attributes: &self.inner,
330            index: 0,
331        }
332    }
333
334    /// Get an iterator over the held [AttributeValue]s. If you also need attribute flags, consider
335    /// using [Attributes::into_attributes_iter] instead.
336    pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
337        self.into_iter()
338    }
339
340    /// Get an iterator over the held [Attribute]s. If you do no not need attribute flags, consider
341    /// using [Attributes::iter] instead.
342    pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
343        self.inner.into_iter()
344    }
345}
346
347pub struct MetaCommunitiesIter<'a> {
348    attributes: &'a [Attribute],
349    index: usize,
350}
351
352impl Iterator for MetaCommunitiesIter<'_> {
353    type Item = MetaCommunity;
354
355    fn next(&mut self) -> Option<Self::Item> {
356        loop {
357            match &self.attributes.first()?.value {
358                AttributeValue::Communities(x) if self.index < x.len() => {
359                    self.index += 1;
360                    return Some(MetaCommunity::Plain(x[self.index - 1]));
361                }
362                AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
363                    self.index += 1;
364                    return Some(MetaCommunity::Extended(x[self.index - 1]));
365                }
366                AttributeValue::LargeCommunities(x) if self.index < x.len() => {
367                    self.index += 1;
368                    return Some(MetaCommunity::Large(x[self.index - 1]));
369                }
370                _ => {
371                    self.attributes = &self.attributes[1..];
372                    self.index = 0;
373                }
374            }
375        }
376    }
377}
378
379fn compute_mask(inner: &[Attribute]) -> [u64; 4] {
380    let mut attr_mask = [0; 4];
381    for attr in inner {
382        let ty = attr.value.attr_code();
383        attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
384    }
385    attr_mask
386}
387
388impl FromIterator<Attribute> for Attributes {
389    fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
390        let inner: Vec<Attribute> = iter.into_iter().collect();
391        let attr_mask = compute_mask(&inner);
392        Attributes {
393            inner,
394            validation_warnings: Vec::new(),
395            attr_mask,
396        }
397    }
398}
399
400impl From<Vec<Attribute>> for Attributes {
401    fn from(value: Vec<Attribute>) -> Self {
402        let attr_mask = compute_mask(&value);
403        Attributes {
404            inner: value,
405            validation_warnings: Vec::new(),
406            attr_mask,
407        }
408    }
409}
410
411impl Extend<Attribute> for Attributes {
412    fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
413        for attr in iter {
414            self.add_attr(attr);
415        }
416    }
417}
418
419impl Extend<AttributeValue> for Attributes {
420    fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
421        self.extend(iter.into_iter().map(Attribute::from))
422    }
423}
424
425impl FromIterator<AttributeValue> for Attributes {
426    fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
427        let inner: Vec<Attribute> = iter.into_iter().map(Attribute::from).collect();
428        let attr_mask = compute_mask(&inner);
429        Attributes {
430            inner,
431            validation_warnings: Vec::new(),
432            attr_mask,
433        }
434    }
435}
436
437impl IntoIterator for Attributes {
438    type Item = AttributeValue;
439    type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
440
441    fn into_iter(self) -> Self::IntoIter {
442        self.inner.into_iter().map(|x| x.value)
443    }
444}
445
446impl<'a> IntoIterator for &'a Attributes {
447    type Item = &'a AttributeValue;
448    type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
449
450    fn into_iter(self) -> Self::IntoIter {
451        self.inner.iter().map(|x| &x.value)
452    }
453}
454
455#[cfg(feature = "serde")]
456mod serde_impl {
457    use super::*;
458    use serde::{Deserialize, Deserializer, Serialize, Serializer};
459
460    impl Serialize for Attributes {
461        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462        where
463            S: Serializer,
464        {
465            self.inner.serialize(serializer)
466        }
467    }
468
469    impl<'de> Deserialize<'de> for Attributes {
470        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471        where
472            D: Deserializer<'de>,
473        {
474            let inner = <Vec<Attribute>>::deserialize(deserializer)?;
475            let attr_mask = compute_mask(&inner);
476            Ok(Attributes {
477                inner,
478                validation_warnings: Vec::new(),
479                attr_mask,
480            })
481        }
482    }
483}
484
485/// BGP Attribute struct with attribute value and flag
486#[derive(Debug, PartialEq, Clone, Eq)]
487#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
488pub struct Attribute {
489    pub value: AttributeValue,
490    pub flag: AttrFlags,
491}
492
493impl Attribute {
494    pub const fn is_optional(&self) -> bool {
495        self.flag.contains(AttrFlags::OPTIONAL)
496    }
497
498    pub const fn is_transitive(&self) -> bool {
499        self.flag.contains(AttrFlags::TRANSITIVE)
500    }
501
502    pub const fn is_partial(&self) -> bool {
503        self.flag.contains(AttrFlags::PARTIAL)
504    }
505
506    pub const fn is_extended(&self) -> bool {
507        self.flag.contains(AttrFlags::EXTENDED)
508    }
509}
510
511impl From<AttributeValue> for Attribute {
512    fn from(value: AttributeValue) -> Self {
513        Attribute {
514            flag: value.default_flags(),
515            value,
516        }
517    }
518}
519
520/// AIGP TLV (Type-Length-Value) entry - RFC 7311
521#[derive(Debug, PartialEq, Clone, Eq)]
522#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
523pub struct AigpTlv {
524    pub tlv_type: u8,
525    pub length: u16,
526    pub value: Bytes,
527}
528
529/// AIGP (Accumulated IGP Metric) Attribute - RFC 7311
530///
531/// Type 26, optional non-transitive attribute containing TLVs.
532/// The AIGP TLV (Type=1) contains an 8-octet accumulated metric value.
533#[derive(Debug, PartialEq, Clone, Eq)]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535pub struct Aigp {
536    pub tlvs: Vec<AigpTlv>,
537}
538
539impl Aigp {
540    /// Get the accumulated metric from the first AIGP TLV (Type=1)
541    pub fn accumulated_metric(&self) -> Option<u64> {
542        self.tlvs
543            .iter()
544            .find(|tlv| tlv.tlv_type == 1)
545            .and_then(|tlv| {
546                if tlv.value.len() >= 8 {
547                    Some(u64::from_be_bytes([
548                        tlv.value[0],
549                        tlv.value[1],
550                        tlv.value[2],
551                        tlv.value[3],
552                        tlv.value[4],
553                        tlv.value[5],
554                        tlv.value[6],
555                        tlv.value[7],
556                    ]))
557                } else {
558                    None
559                }
560            })
561    }
562}
563
564/// Raw TLV with 1-octet type and 1-octet value length.
565#[derive(Debug, PartialEq, Clone, Eq)]
566#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
567pub struct RawTlv8 {
568    pub tlv_type: u8,
569    pub value: Bytes,
570}
571
572/// Raw TLV with 1-octet type and 2-octet value length.
573#[derive(Debug, PartialEq, Clone, Eq)]
574#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
575pub struct RawTlv8Ext {
576    pub tlv_type: u8,
577    pub value: Bytes,
578}
579
580/// Raw TLV with 2-octet type and 2-octet value length.
581#[derive(Debug, PartialEq, Clone, Eq)]
582#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
583pub struct RawTlv16 {
584    pub tlv_type: u16,
585    pub value: Bytes,
586}
587
588/// BFD Discriminator Attribute - RFC 9026
589#[derive(Debug, PartialEq, Clone, Eq)]
590#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
591pub struct BfdDiscriminatorAttribute {
592    pub mode: u8,
593    pub discriminator: u32,
594    pub tlvs: Vec<RawTlv8>,
595}
596
597/// BGP Prefix-SID Attribute - RFC 8669
598#[derive(Debug, PartialEq, Clone, Eq)]
599#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
600pub struct BgpPrefixSidAttribute {
601    pub tlvs: Vec<RawTlv8Ext>,
602}
603
604/// BIER Attribute - RFC 9793
605#[derive(Debug, PartialEq, Clone, Eq)]
606#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
607pub struct BierAttribute {
608    pub tlvs: Vec<RawTlv16>,
609}
610
611/// SFP Attribute - RFC 9015
612#[derive(Debug, PartialEq, Clone, Eq)]
613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
614pub struct SfpAttribute {
615    pub tlvs: Vec<RawTlv8Ext>,
616}
617
618/// ATTR_SET Attribute - RFC 6368
619///
620/// Used in BGP/MPLS IP VPNs to transparently carry customer BGP path attributes
621/// through the VPN core. Acts as a stack where attributes are "pushed" at the
622/// PE ingress and "popped" at the PE egress.
623///
624/// Type 128, optional transitive attribute.
625#[derive(Debug, PartialEq, Clone, Eq)]
626#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
627pub struct AttrSet {
628    /// Origin AS number (customer network AS)
629    pub origin_as: Asn,
630    /// Nested path attributes
631    pub attributes: Attributes,
632}
633
634/// The `AttributeValue` enum represents different kinds of Attribute values.
635#[derive(Debug, PartialEq, Clone, Eq)]
636#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
637pub enum AttributeValue {
638    Origin(Origin),
639    AsPath {
640        path: AsPath,
641        is_as4: bool,
642    },
643    NextHop(IpAddr),
644    MultiExitDiscriminator(u32),
645    LocalPreference(u32),
646    OnlyToCustomer(Asn),
647    AtomicAggregate,
648    Aggregator {
649        asn: Asn,
650        id: BgpIdentifier,
651        is_as4: bool,
652    },
653    Communities(Vec<Community>),
654    ExtendedCommunities(Vec<ExtendedCommunity>),
655    Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
656    LargeCommunities(Vec<LargeCommunity>),
657    OriginatorId(BgpIdentifier),
658    Clusters(Vec<u32>),
659    MpReachNlri(Nlri),
660    MpUnreachNlri(Nlri),
661    /// BGP Link-State attribute - RFC 7752
662    LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
663    /// BGP Tunnel Encapsulation attribute - RFC 9012
664    TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
665    /// BFD Discriminator attribute - RFC 9026
666    BfdDiscriminator(BfdDiscriminatorAttribute),
667    /// BGP Prefix-SID attribute - RFC 8669
668    BgpPrefixSid(BgpPrefixSidAttribute),
669    /// BIER attribute - RFC 9793
670    Bier(BierAttribute),
671    /// SFP attribute - RFC 9015
672    Sfp(SfpAttribute),
673    Development(Vec<u8>),
674    Raw(AttrRaw),
675    Deprecated(AttrRaw),
676    Unknown(AttrRaw),
677    /// AIGP (Accumulated IGP Metric) attribute - RFC 7311
678    Aigp(Aigp),
679    /// ATTR_SET attribute - RFC 6368
680    AttrSet(AttrSet),
681}
682
683impl From<Origin> for AttributeValue {
684    fn from(value: Origin) -> Self {
685        AttributeValue::Origin(value)
686    }
687}
688
689/// Defaults to using `AS_PATH` (as opposed to `AS4_PATH`) when choosing attribute type.
690impl From<AsPath> for AttributeValue {
691    fn from(path: AsPath) -> Self {
692        AttributeValue::AsPath {
693            path,
694            is_as4: false,
695        }
696    }
697}
698
699/// Category of an attribute.
700///
701/// <https://datatracker.ietf.org/doc/html/rfc4271#section-5>
702#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
703#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
704pub enum AttributeCategory {
705    WellKnownMandatory,
706    WellKnownDiscretionary,
707    OptionalTransitive,
708    OptionalNonTransitive,
709}
710
711impl AttributeValue {
712    pub fn attr_type(&self) -> AttrType {
713        match self {
714            AttributeValue::Origin(_) => AttrType::ORIGIN,
715            AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
716            AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
717            AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
718            AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
719            AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
720            AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
721            AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
722            AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
723            AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
724            AttributeValue::Communities(_) => AttrType::COMMUNITIES,
725            AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
726            AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
727                AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
728            }
729            AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
730            AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
731            AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
732            AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
733            AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
734            AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
735            AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
736            AttributeValue::BfdDiscriminator(_) => AttrType::BFD_DISCRIMINATOR,
737            AttributeValue::BgpPrefixSid(_) => AttrType::BGP_PREFIX_SID,
738            AttributeValue::Bier(_) => AttrType::BIER,
739            AttributeValue::Sfp(_) => AttrType::SFP_ATTRIBUTE,
740            AttributeValue::Development(_) => AttrType::DEVELOPMENT,
741            AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
742                x.attr_type()
743            }
744            AttributeValue::Aigp(_) => AttrType::AIGP,
745            AttributeValue::AttrSet(_) => AttrType::ATTR_SET,
746        }
747    }
748
749    pub fn attr_code(&self) -> u8 {
750        match self {
751            AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
752                x.code
753            }
754            _ => self.attr_type().into(),
755        }
756    }
757
758    pub fn attr_category(&self) -> Option<AttributeCategory> {
759        use AttributeCategory::*;
760
761        match self {
762            AttributeValue::Origin(_) => Some(WellKnownMandatory),
763            AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
764            AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
765            AttributeValue::NextHop(_) => Some(WellKnownMandatory),
766            AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
767            // If we receive this attribute we must be in IBGP so it is required
768            AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
769            AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
770            AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
771            AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
772            AttributeValue::Communities(_) => Some(OptionalTransitive),
773            AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
774            AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
775            AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
776            AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
777            AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
778            AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
779            AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
780            AttributeValue::Aigp(_) => Some(OptionalNonTransitive),
781            AttributeValue::BfdDiscriminator(_) => Some(OptionalTransitive),
782            AttributeValue::BgpPrefixSid(_) => Some(OptionalTransitive),
783            AttributeValue::Bier(_) => Some(OptionalTransitive),
784            AttributeValue::Sfp(_) => Some(OptionalTransitive),
785            AttributeValue::AttrSet(_) => Some(OptionalTransitive),
786            _ => None,
787        }
788    }
789
790    /// Get flags based on the attribute type. The [AttrFlags::EXTENDED] is not taken into account
791    /// when determining the correct flags.
792    pub fn default_flags(&self) -> AttrFlags {
793        match self.attr_category() {
794            None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
795            Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
796            Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
797            Some(AttributeCategory::OptionalTransitive) => {
798                AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
799            }
800            Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
801        }
802    }
803}
804
805#[derive(Debug, PartialEq, Clone, Eq)]
806#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
807pub struct AttrRaw {
808    pub code: u8,
809    pub bytes: Bytes,
810}
811
812impl AttrRaw {
813    /// Map the raw wire code back to an `AttrType`.
814    ///
815    /// For `Raw` variants (known-but-unparsed codes like `PMSI_TUNNEL`),
816    /// this returns the concrete `AttrType` variant (e.g. `AttrType::PMSI_TUNNEL`).
817    /// For `Deprecated` and `Unknown` variants, this returns `AttrType::Unknown(code)`.
818    pub fn attr_type(&self) -> AttrType {
819        AttrType::from(self.code)
820    }
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826    use std::net::Ipv4Addr;
827    use std::str::FromStr;
828
829    #[test]
830    fn test_attr_type() {
831        let attr_value = AttributeValue::Origin(Origin::IGP);
832        assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
833    }
834
835    #[test]
836    fn test_attr_category() {
837        let attr_value = AttributeValue::Origin(Origin::IGP);
838        let category = attr_value.attr_category().unwrap();
839        assert_eq!(category, AttributeCategory::WellKnownMandatory);
840    }
841
842    #[test]
843    fn test_default_flags() {
844        let attr_value = AttributeValue::Origin(Origin::IGP);
845        let flags = attr_value.default_flags();
846        assert_eq!(flags, AttrFlags::TRANSITIVE);
847    }
848
849    #[test]
850    fn test_from_iter_attribute_value_uses_default_flags() {
851        let attributes = Attributes::from_iter(vec![
852            AttributeValue::Origin(Origin::IGP),
853            AttributeValue::AsPath {
854                path: AsPath::new(),
855                is_as4: false,
856            },
857        ]);
858
859        assert_eq!(
860            attributes.get_attr(AttrType::ORIGIN).unwrap().flag,
861            AttrFlags::TRANSITIVE
862        );
863        assert_eq!(
864            attributes.get_attr(AttrType::AS_PATH).unwrap().flag,
865            AttrFlags::TRANSITIVE
866        );
867    }
868
869    #[test]
870    fn test_get_attr() {
871        let attribute = Attribute {
872            value: AttributeValue::Origin(Origin::IGP),
873            flag: AttrFlags::TRANSITIVE,
874        };
875
876        let mut attributes = Attributes::default();
877        attributes.add_attr(attribute.clone());
878
879        assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
880    }
881
882    #[test]
883    fn test_has_attr() {
884        let attribute = Attribute {
885            value: AttributeValue::Origin(Origin::IGP),
886            flag: AttrFlags::TRANSITIVE,
887        };
888
889        let mut attributes = Attributes::default();
890        attributes.add_attr(attribute);
891
892        assert!(attributes.has_attr(AttrType::ORIGIN));
893    }
894
895    #[test]
896    fn test_getting_all_attributes() {
897        let mut attributes = Attributes::default();
898        attributes.add_attr(Attribute {
899            value: AttributeValue::Origin(Origin::IGP),
900            flag: AttrFlags::TRANSITIVE,
901        });
902        attributes.add_attr(Attribute {
903            value: AttributeValue::AsPath {
904                path: AsPath::new(),
905                is_as4: false,
906            },
907            flag: AttrFlags::TRANSITIVE,
908        });
909        attributes.add_attr(Attribute {
910            value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
911            flag: AttrFlags::TRANSITIVE,
912        });
913        attributes.add_attr(Attribute {
914            value: AttributeValue::MultiExitDiscriminator(1),
915            flag: AttrFlags::TRANSITIVE,
916        });
917
918        attributes.add_attr(Attribute {
919            value: AttributeValue::LocalPreference(1),
920            flag: AttrFlags::TRANSITIVE,
921        });
922        attributes.add_attr(Attribute {
923            value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
924            flag: AttrFlags::TRANSITIVE,
925        });
926        attributes.add_attr(Attribute {
927            value: AttributeValue::AtomicAggregate,
928            flag: AttrFlags::TRANSITIVE,
929        });
930        attributes.add_attr(Attribute {
931            value: AttributeValue::Clusters(vec![1, 2, 3]),
932            flag: AttrFlags::TRANSITIVE,
933        });
934        attributes.add_attr(Attribute {
935            value: AttributeValue::Aggregator {
936                asn: Asn::new_32bit(1),
937                id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
938                is_as4: false,
939            },
940            flag: AttrFlags::TRANSITIVE,
941        });
942        attributes.add_attr(Attribute {
943            value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
944            flag: AttrFlags::TRANSITIVE,
945        });
946
947        assert_eq!(attributes.origin(), Origin::IGP);
948        assert_eq!(attributes.as_path(), Some(&AsPath::new()));
949        assert_eq!(
950            attributes.next_hop(),
951            Some(IpAddr::from_str("10.0.0.0").unwrap())
952        );
953        assert_eq!(attributes.multi_exit_discriminator(), Some(1));
954        assert_eq!(attributes.local_preference(), Some(1));
955        assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
956        assert!(attributes.atomic_aggregate());
957        assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
958        assert_eq!(
959            attributes.aggregator(),
960            Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
961        );
962        assert_eq!(
963            attributes.origin_id(),
964            Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
965        );
966
967        let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
968        assert!(aspath_attr.is_transitive());
969        assert!(!aspath_attr.is_extended());
970        assert!(!aspath_attr.is_partial());
971        assert!(!aspath_attr.is_optional());
972
973        for attr in attributes.iter() {
974            println!("{attr:?}");
975        }
976    }
977
978    #[test]
979    fn test_from() {
980        let origin = Origin::IGP;
981        let attr_value = AttributeValue::from(origin);
982        assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
983
984        let aspath = AsPath::new();
985        let attr_value = AttributeValue::from(aspath);
986        assert_eq!(
987            attr_value,
988            AttributeValue::AsPath {
989                path: AsPath::new(),
990                is_as4: false
991            }
992        );
993    }
994
995    #[test]
996    fn test_well_known_mandatory_attrs() {
997        let origin_attr = AttributeValue::Origin(Origin::IGP);
998        assert_eq!(
999            origin_attr.attr_category(),
1000            Some(AttributeCategory::WellKnownMandatory)
1001        );
1002        let as_path_attr = AttributeValue::AsPath {
1003            path: AsPath::new(),
1004            is_as4: false,
1005        };
1006        assert_eq!(
1007            as_path_attr.attr_category(),
1008            Some(AttributeCategory::WellKnownMandatory)
1009        );
1010        let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
1011        assert_eq!(
1012            next_hop_attr.attr_category(),
1013            Some(AttributeCategory::WellKnownMandatory)
1014        );
1015        let local_preference_attr = AttributeValue::LocalPreference(1);
1016        assert_eq!(
1017            local_preference_attr.attr_category(),
1018            Some(AttributeCategory::WellKnownMandatory)
1019        );
1020    }
1021
1022    #[test]
1023    fn test_well_known_discretionary_attrs() {
1024        let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
1025        assert_eq!(
1026            atomic_aggregate_attr.attr_category(),
1027            Some(AttributeCategory::WellKnownDiscretionary)
1028        );
1029    }
1030
1031    #[test]
1032    fn test_optional_transitive_attrs() {
1033        let as_path_attr = AttributeValue::AsPath {
1034            path: AsPath::new(),
1035            is_as4: true,
1036        };
1037        assert_eq!(
1038            as_path_attr.attr_category(),
1039            Some(AttributeCategory::OptionalTransitive)
1040        );
1041        let aggregator_attr = AttributeValue::Aggregator {
1042            asn: Asn::new_32bit(1),
1043            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1044            is_as4: false,
1045        };
1046        assert_eq!(
1047            aggregator_attr.attr_category(),
1048            Some(AttributeCategory::OptionalTransitive)
1049        );
1050        let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
1051        assert_eq!(
1052            only_to_customer_attr.attr_category(),
1053            Some(AttributeCategory::OptionalTransitive)
1054        );
1055        let communities_attr =
1056            AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
1057        assert_eq!(
1058            communities_attr.attr_category(),
1059            Some(AttributeCategory::OptionalTransitive)
1060        );
1061        let extended_communities_attr =
1062            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
1063        assert_eq!(
1064            extended_communities_attr.attr_category(),
1065            Some(AttributeCategory::OptionalTransitive)
1066        );
1067        let large_communities_attr =
1068            AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
1069        assert_eq!(
1070            large_communities_attr.attr_category(),
1071            Some(AttributeCategory::OptionalTransitive)
1072        );
1073        let aggregator_attr = AttributeValue::Aggregator {
1074            asn: Asn::new_32bit(1),
1075            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1076            is_as4: true,
1077        };
1078        assert_eq!(
1079            aggregator_attr.attr_category(),
1080            Some(AttributeCategory::OptionalTransitive)
1081        );
1082    }
1083
1084    #[test]
1085    fn test_new_attribute_attr_categories() {
1086        // BFD Discriminator (RFC 9026): Optional Transitive
1087        assert_eq!(
1088            AttributeValue::BfdDiscriminator(BfdDiscriminatorAttribute {
1089                mode: 1,
1090                discriminator: 0,
1091                tlvs: vec![],
1092            })
1093            .attr_category(),
1094            Some(AttributeCategory::OptionalTransitive)
1095        );
1096        // BGP Prefix-SID (RFC 8669): Optional Transitive
1097        assert_eq!(
1098            AttributeValue::BgpPrefixSid(BgpPrefixSidAttribute { tlvs: vec![] }).attr_category(),
1099            Some(AttributeCategory::OptionalTransitive)
1100        );
1101        // BIER (RFC 9793): Optional Transitive
1102        assert_eq!(
1103            AttributeValue::Bier(BierAttribute { tlvs: vec![] }).attr_category(),
1104            Some(AttributeCategory::OptionalTransitive)
1105        );
1106        // SFP (RFC 9015): Optional Transitive
1107        assert_eq!(
1108            AttributeValue::Sfp(SfpAttribute { tlvs: vec![] }).attr_category(),
1109            Some(AttributeCategory::OptionalTransitive)
1110        );
1111    }
1112
1113    #[test]
1114    fn test_optional_non_transitive_attrs() {
1115        let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
1116        assert_eq!(
1117            multi_exit_discriminator_attr.attr_category(),
1118            Some(AttributeCategory::OptionalNonTransitive)
1119        );
1120        let originator_id_attr =
1121            AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
1122        assert_eq!(
1123            originator_id_attr.attr_category(),
1124            Some(AttributeCategory::OptionalNonTransitive)
1125        );
1126        let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
1127        assert_eq!(
1128            clusters_attr.attr_category(),
1129            Some(AttributeCategory::OptionalNonTransitive)
1130        );
1131        let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
1132            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1133        ));
1134        assert_eq!(
1135            mp_unreach_nlri_attr.attr_category(),
1136            Some(AttributeCategory::OptionalNonTransitive)
1137        );
1138
1139        let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1140            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1141        ));
1142        assert_eq!(
1143            mp_reach_nlri_attr.attr_category(),
1144            Some(AttributeCategory::OptionalNonTransitive)
1145        );
1146    }
1147
1148    #[test]
1149    #[cfg(feature = "serde")]
1150    fn test_serde() {
1151        let attributes = Attributes::from_iter(vec![
1152            Attribute {
1153                value: AttributeValue::Origin(Origin::IGP),
1154                flag: AttrFlags::TRANSITIVE,
1155            },
1156            Attribute {
1157                value: AttributeValue::AsPath {
1158                    path: AsPath::new(),
1159                    is_as4: false,
1160                },
1161                flag: AttrFlags::TRANSITIVE,
1162            },
1163        ]);
1164
1165        let serialized = serde_json::to_string(&attributes).unwrap();
1166        let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
1167
1168        assert_eq!(attributes, deserialized);
1169    }
1170}