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