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(Debug, 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}
145
146impl Attributes {
147    pub fn has_attr(&self, ty: AttrType) -> bool {
148        self.inner.iter().any(|x| x.value.attr_type() == ty)
149    }
150
151    pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
152        self.inner
153            .iter()
154            .find(|x| x.value.attr_type() == ty)
155            .cloned()
156    }
157
158    pub fn add_attr(&mut self, attr: Attribute) {
159        self.inner.push(attr);
160    }
161
162    /// Add a validation warning to the attributes
163    pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
164        self.validation_warnings.push(warning);
165    }
166
167    /// Get all validation warnings for these attributes
168    pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
169        &self.validation_warnings
170    }
171
172    /// Check if there are any validation warnings
173    pub fn has_validation_warnings(&self) -> bool {
174        !self.validation_warnings.is_empty()
175    }
176
177    /// Get the `ORIGIN` attribute. In the event that this attribute is not present,
178    /// [Origin::INCOMPLETE] will be returned instead.
179    pub fn origin(&self) -> Origin {
180        self.inner
181            .iter()
182            .find_map(|x| match &x.value {
183                AttributeValue::Origin(x) => Some(*x),
184                _ => None,
185            })
186            .unwrap_or(Origin::INCOMPLETE)
187    }
188
189    /// Get the `ORIGINATOR_ID` attribute if present.
190    pub fn origin_id(&self) -> Option<BgpIdentifier> {
191        self.inner.iter().find_map(|x| match &x.value {
192            AttributeValue::OriginatorId(x) => Some(*x),
193            _ => None,
194        })
195    }
196
197    /// Get the `NEXT_HOP` attribute if present.
198    ///
199    /// **Note**: Even when this attribute is not present, the next hop address may still be
200    /// attainable from the `MP_REACH_NLRI` attribute.
201    pub fn next_hop(&self) -> Option<IpAddr> {
202        self.inner.iter().find_map(|x| match &x.value {
203            AttributeValue::NextHop(x) => Some(*x),
204            _ => None,
205        })
206    }
207
208    pub fn multi_exit_discriminator(&self) -> Option<u32> {
209        self.inner.iter().find_map(|x| match &x.value {
210            AttributeValue::MultiExitDiscriminator(x) => Some(*x),
211            _ => None,
212        })
213    }
214
215    pub fn local_preference(&self) -> Option<u32> {
216        self.inner.iter().find_map(|x| match &x.value {
217            AttributeValue::LocalPreference(x) => Some(*x),
218            _ => None,
219        })
220    }
221
222    pub fn only_to_customer(&self) -> Option<Asn> {
223        self.inner.iter().find_map(|x| match &x.value {
224            AttributeValue::OnlyToCustomer(x) => Some(*x),
225            _ => None,
226        })
227    }
228
229    pub fn atomic_aggregate(&self) -> bool {
230        self.inner
231            .iter()
232            .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
233    }
234
235    pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
236        // Begin searching at the end of the attributes to increase the odds of finding an AS4
237        // attribute first.
238        self.inner.iter().rev().find_map(|x| match &x.value {
239            AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
240            _ => None,
241        })
242    }
243
244    pub fn clusters(&self) -> Option<&[u32]> {
245        self.inner.iter().find_map(|x| match &x.value {
246            AttributeValue::Clusters(x) => Some(x.as_ref()),
247            _ => None,
248        })
249    }
250
251    // These implementations are horribly inefficient, but they were super easy to write and use
252    pub fn as_path(&self) -> Option<&AsPath> {
253        // Begin searching at the end of the attributes to increase the odds of finding an AS4
254        // attribute first.
255        self.inner.iter().rev().find_map(|x| match &x.value {
256            AttributeValue::AsPath { path, .. } => Some(path),
257            _ => None,
258        })
259    }
260
261    pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
262        self.inner.iter().find_map(|x| match &x.value {
263            AttributeValue::MpReachNlri(x) => Some(x),
264            _ => None,
265        })
266    }
267
268    pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
269        self.inner.iter().find_map(|x| match &x.value {
270            AttributeValue::MpUnreachNlri(x) => Some(x),
271            _ => None,
272        })
273    }
274
275    pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
276        MetaCommunitiesIter {
277            attributes: &self.inner,
278            index: 0,
279        }
280    }
281
282    /// Get an iterator over the held [AttributeValue]s. If you also need attribute flags, consider
283    /// using [Attributes::into_attributes_iter] instead.
284    pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
285        self.into_iter()
286    }
287
288    /// Get an iterator over the held [Attribute]s. If you do no not need attribute flags, consider
289    /// using [Attributes::iter] instead.
290    pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
291        self.inner.into_iter()
292    }
293}
294
295pub struct MetaCommunitiesIter<'a> {
296    attributes: &'a [Attribute],
297    index: usize,
298}
299
300impl Iterator for MetaCommunitiesIter<'_> {
301    type Item = MetaCommunity;
302
303    fn next(&mut self) -> Option<Self::Item> {
304        loop {
305            match &self.attributes.first()?.value {
306                AttributeValue::Communities(x) if self.index < x.len() => {
307                    self.index += 1;
308                    return Some(MetaCommunity::Plain(x[self.index - 1]));
309                }
310                AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
311                    self.index += 1;
312                    return Some(MetaCommunity::Extended(x[self.index - 1]));
313                }
314                AttributeValue::LargeCommunities(x) if self.index < x.len() => {
315                    self.index += 1;
316                    return Some(MetaCommunity::Large(x[self.index - 1]));
317                }
318                _ => {
319                    self.attributes = &self.attributes[1..];
320                    self.index = 0;
321                }
322            }
323        }
324    }
325}
326
327impl FromIterator<Attribute> for Attributes {
328    fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
329        Attributes {
330            inner: iter.into_iter().collect(),
331            validation_warnings: Vec::new(),
332        }
333    }
334}
335
336impl From<Vec<Attribute>> for Attributes {
337    fn from(value: Vec<Attribute>) -> Self {
338        Attributes {
339            inner: value,
340            validation_warnings: Vec::new(),
341        }
342    }
343}
344
345impl Extend<Attribute> for Attributes {
346    fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
347        self.inner.extend(iter)
348    }
349}
350
351impl Extend<AttributeValue> for Attributes {
352    fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
353        self.extend(iter.into_iter().map(Attribute::from))
354    }
355}
356
357impl FromIterator<AttributeValue> for Attributes {
358    fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
359        Attributes {
360            inner: iter.into_iter().map(Attribute::from).collect(),
361            validation_warnings: Vec::new(),
362        }
363    }
364}
365
366impl IntoIterator for Attributes {
367    type Item = AttributeValue;
368    type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
369
370    fn into_iter(self) -> Self::IntoIter {
371        self.inner.into_iter().map(|x| x.value)
372    }
373}
374
375impl<'a> IntoIterator for &'a Attributes {
376    type Item = &'a AttributeValue;
377    type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
378
379    fn into_iter(self) -> Self::IntoIter {
380        self.inner.iter().map(|x| &x.value)
381    }
382}
383
384#[cfg(feature = "serde")]
385mod serde_impl {
386    use super::*;
387    use serde::{Deserialize, Deserializer, Serialize, Serializer};
388
389    impl Serialize for Attributes {
390        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
391        where
392            S: Serializer,
393        {
394            self.inner.serialize(serializer)
395        }
396    }
397
398    impl<'de> Deserialize<'de> for Attributes {
399        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
400        where
401            D: Deserializer<'de>,
402        {
403            Ok(Attributes {
404                inner: <Vec<Attribute>>::deserialize(deserializer)?,
405                validation_warnings: Vec::new(),
406            })
407        }
408    }
409}
410
411/// BGP Attribute struct with attribute value and flag
412#[derive(Debug, PartialEq, Clone, Eq)]
413#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
414pub struct Attribute {
415    pub value: AttributeValue,
416    pub flag: AttrFlags,
417}
418
419impl Attribute {
420    pub const fn is_optional(&self) -> bool {
421        self.flag.contains(AttrFlags::OPTIONAL)
422    }
423
424    pub const fn is_transitive(&self) -> bool {
425        self.flag.contains(AttrFlags::TRANSITIVE)
426    }
427
428    pub const fn is_partial(&self) -> bool {
429        self.flag.contains(AttrFlags::PARTIAL)
430    }
431
432    pub const fn is_extended(&self) -> bool {
433        self.flag.contains(AttrFlags::EXTENDED)
434    }
435}
436
437impl From<AttributeValue> for Attribute {
438    fn from(value: AttributeValue) -> Self {
439        Attribute {
440            flag: value.default_flags(),
441            value,
442        }
443    }
444}
445
446/// AIGP TLV (Type-Length-Value) entry - RFC 7311
447#[derive(Debug, PartialEq, Clone, Eq)]
448#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
449pub struct AigpTlv {
450    pub tlv_type: u8,
451    pub length: u16,
452    pub value: Vec<u8>,
453}
454
455/// AIGP (Accumulated IGP Metric) Attribute - RFC 7311
456///
457/// Type 26, optional non-transitive attribute containing TLVs.
458/// The AIGP TLV (Type=1) contains an 8-octet accumulated metric value.
459#[derive(Debug, PartialEq, Clone, Eq)]
460#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
461pub struct Aigp {
462    pub tlvs: Vec<AigpTlv>,
463}
464
465impl Aigp {
466    /// Get the accumulated metric from the first AIGP TLV (Type=1)
467    pub fn accumulated_metric(&self) -> Option<u64> {
468        self.tlvs
469            .iter()
470            .find(|tlv| tlv.tlv_type == 1)
471            .and_then(|tlv| {
472                if tlv.value.len() >= 8 {
473                    Some(u64::from_be_bytes([
474                        tlv.value[0],
475                        tlv.value[1],
476                        tlv.value[2],
477                        tlv.value[3],
478                        tlv.value[4],
479                        tlv.value[5],
480                        tlv.value[6],
481                        tlv.value[7],
482                    ]))
483                } else {
484                    None
485                }
486            })
487    }
488}
489
490/// ATTR_SET Attribute - RFC 6368
491///
492/// Used in BGP/MPLS IP VPNs to transparently carry customer BGP path attributes
493/// through the VPN core. Acts as a stack where attributes are "pushed" at the
494/// PE ingress and "popped" at the PE egress.
495///
496/// Type 128, optional transitive attribute.
497#[derive(Debug, PartialEq, Clone, Eq)]
498#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
499pub struct AttrSet {
500    /// Origin AS number (customer network AS)
501    pub origin_as: Asn,
502    /// Nested path attributes
503    pub attributes: Attributes,
504}
505
506/// The `AttributeValue` enum represents different kinds of Attribute values.
507#[derive(Debug, PartialEq, Clone, Eq)]
508#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
509pub enum AttributeValue {
510    Origin(Origin),
511    AsPath {
512        path: AsPath,
513        is_as4: bool,
514    },
515    NextHop(IpAddr),
516    MultiExitDiscriminator(u32),
517    LocalPreference(u32),
518    OnlyToCustomer(Asn),
519    AtomicAggregate,
520    Aggregator {
521        asn: Asn,
522        id: BgpIdentifier,
523        is_as4: bool,
524    },
525    Communities(Vec<Community>),
526    ExtendedCommunities(Vec<ExtendedCommunity>),
527    Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
528    LargeCommunities(Vec<LargeCommunity>),
529    OriginatorId(BgpIdentifier),
530    Clusters(Vec<u32>),
531    MpReachNlri(Nlri),
532    MpUnreachNlri(Nlri),
533    /// BGP Link-State attribute - RFC 7752
534    LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
535    /// BGP Tunnel Encapsulation attribute - RFC 9012
536    TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
537    Development(Vec<u8>),
538    Deprecated(AttrRaw),
539    Unknown(AttrRaw),
540    /// AIGP (Accumulated IGP Metric) attribute - RFC 7311
541    Aigp(Aigp),
542    /// ATTR_SET attribute - RFC 6368
543    AttrSet(AttrSet),
544}
545
546impl From<Origin> for AttributeValue {
547    fn from(value: Origin) -> Self {
548        AttributeValue::Origin(value)
549    }
550}
551
552/// Defaults to using `AS_PATH` (as opposed to `AS4_PATH`) when choosing attribute type.
553impl From<AsPath> for AttributeValue {
554    fn from(path: AsPath) -> Self {
555        AttributeValue::AsPath {
556            path,
557            is_as4: false,
558        }
559    }
560}
561
562/// Category of an attribute.
563///
564/// <https://datatracker.ietf.org/doc/html/rfc4271#section-5>
565#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
566#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
567pub enum AttributeCategory {
568    WellKnownMandatory,
569    WellKnownDiscretionary,
570    OptionalTransitive,
571    OptionalNonTransitive,
572}
573
574impl AttributeValue {
575    pub const fn attr_type(&self) -> AttrType {
576        match self {
577            AttributeValue::Origin(_) => AttrType::ORIGIN,
578            AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
579            AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
580            AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
581            AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
582            AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
583            AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
584            AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
585            AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
586            AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
587            AttributeValue::Communities(_) => AttrType::COMMUNITIES,
588            AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
589            AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
590                AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
591            }
592            AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
593            AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
594            AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
595            AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
596            AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
597            AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
598            AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
599            AttributeValue::Development(_) => AttrType::DEVELOPMENT,
600            AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => x.attr_type,
601            AttributeValue::Aigp(_) => AttrType::AIGP,
602            AttributeValue::AttrSet(_) => AttrType::ATTR_SET,
603        }
604    }
605
606    pub fn attr_category(&self) -> Option<AttributeCategory> {
607        use AttributeCategory::*;
608
609        match self {
610            AttributeValue::Origin(_) => Some(WellKnownMandatory),
611            AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
612            AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
613            AttributeValue::NextHop(_) => Some(WellKnownMandatory),
614            AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
615            // If we receive this attribute we must be in IBGP so it is required
616            AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
617            AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
618            AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
619            AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
620            AttributeValue::Communities(_) => Some(OptionalTransitive),
621            AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
622            AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
623            AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
624            AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
625            AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
626            AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
627            AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
628            AttributeValue::Aigp(_) => Some(OptionalNonTransitive),
629            AttributeValue::AttrSet(_) => Some(OptionalTransitive),
630            _ => None,
631        }
632    }
633
634    /// Get flags based on the attribute type. The [AttrFlags::EXTENDED] is not taken into account
635    /// when determining the correct flags.
636    pub fn default_flags(&self) -> AttrFlags {
637        match self.attr_category() {
638            None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
639            Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
640            Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
641            Some(AttributeCategory::OptionalTransitive) => {
642                AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
643            }
644            Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
645        }
646    }
647}
648
649#[derive(Debug, PartialEq, Clone, Eq)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
651pub struct AttrRaw {
652    pub attr_type: AttrType,
653    pub bytes: Vec<u8>,
654}
655
656#[cfg(test)]
657mod tests {
658    use super::*;
659    use std::net::Ipv4Addr;
660    use std::str::FromStr;
661
662    #[test]
663    fn test_attr_type() {
664        let attr_value = AttributeValue::Origin(Origin::IGP);
665        assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
666    }
667
668    #[test]
669    fn test_attr_category() {
670        let attr_value = AttributeValue::Origin(Origin::IGP);
671        let category = attr_value.attr_category().unwrap();
672        assert_eq!(category, AttributeCategory::WellKnownMandatory);
673    }
674
675    #[test]
676    fn test_default_flags() {
677        let attr_value = AttributeValue::Origin(Origin::IGP);
678        let flags = attr_value.default_flags();
679        assert_eq!(flags, AttrFlags::TRANSITIVE);
680    }
681
682    #[test]
683    fn test_from_iter_attribute_value_uses_default_flags() {
684        let attributes = Attributes::from_iter(vec![
685            AttributeValue::Origin(Origin::IGP),
686            AttributeValue::AsPath {
687                path: AsPath::new(),
688                is_as4: false,
689            },
690        ]);
691
692        assert_eq!(
693            attributes.get_attr(AttrType::ORIGIN).unwrap().flag,
694            AttrFlags::TRANSITIVE
695        );
696        assert_eq!(
697            attributes.get_attr(AttrType::AS_PATH).unwrap().flag,
698            AttrFlags::TRANSITIVE
699        );
700    }
701
702    #[test]
703    fn test_get_attr() {
704        let attribute = Attribute {
705            value: AttributeValue::Origin(Origin::IGP),
706            flag: AttrFlags::TRANSITIVE,
707        };
708
709        let mut attributes = Attributes::default();
710        attributes.add_attr(attribute.clone());
711
712        assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
713    }
714
715    #[test]
716    fn test_has_attr() {
717        let attribute = Attribute {
718            value: AttributeValue::Origin(Origin::IGP),
719            flag: AttrFlags::TRANSITIVE,
720        };
721
722        let mut attributes = Attributes::default();
723        attributes.add_attr(attribute);
724
725        assert!(attributes.has_attr(AttrType::ORIGIN));
726    }
727
728    #[test]
729    fn test_getting_all_attributes() {
730        let mut attributes = Attributes::default();
731        attributes.add_attr(Attribute {
732            value: AttributeValue::Origin(Origin::IGP),
733            flag: AttrFlags::TRANSITIVE,
734        });
735        attributes.add_attr(Attribute {
736            value: AttributeValue::AsPath {
737                path: AsPath::new(),
738                is_as4: false,
739            },
740            flag: AttrFlags::TRANSITIVE,
741        });
742        attributes.add_attr(Attribute {
743            value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
744            flag: AttrFlags::TRANSITIVE,
745        });
746        attributes.add_attr(Attribute {
747            value: AttributeValue::MultiExitDiscriminator(1),
748            flag: AttrFlags::TRANSITIVE,
749        });
750
751        attributes.add_attr(Attribute {
752            value: AttributeValue::LocalPreference(1),
753            flag: AttrFlags::TRANSITIVE,
754        });
755        attributes.add_attr(Attribute {
756            value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
757            flag: AttrFlags::TRANSITIVE,
758        });
759        attributes.add_attr(Attribute {
760            value: AttributeValue::AtomicAggregate,
761            flag: AttrFlags::TRANSITIVE,
762        });
763        attributes.add_attr(Attribute {
764            value: AttributeValue::Clusters(vec![1, 2, 3]),
765            flag: AttrFlags::TRANSITIVE,
766        });
767        attributes.add_attr(Attribute {
768            value: AttributeValue::Aggregator {
769                asn: Asn::new_32bit(1),
770                id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
771                is_as4: false,
772            },
773            flag: AttrFlags::TRANSITIVE,
774        });
775        attributes.add_attr(Attribute {
776            value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
777            flag: AttrFlags::TRANSITIVE,
778        });
779
780        assert_eq!(attributes.origin(), Origin::IGP);
781        assert_eq!(attributes.as_path(), Some(&AsPath::new()));
782        assert_eq!(
783            attributes.next_hop(),
784            Some(IpAddr::from_str("10.0.0.0").unwrap())
785        );
786        assert_eq!(attributes.multi_exit_discriminator(), Some(1));
787        assert_eq!(attributes.local_preference(), Some(1));
788        assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
789        assert!(attributes.atomic_aggregate());
790        assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
791        assert_eq!(
792            attributes.aggregator(),
793            Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
794        );
795        assert_eq!(
796            attributes.origin_id(),
797            Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
798        );
799
800        let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
801        assert!(aspath_attr.is_transitive());
802        assert!(!aspath_attr.is_extended());
803        assert!(!aspath_attr.is_partial());
804        assert!(!aspath_attr.is_optional());
805
806        for attr in attributes.iter() {
807            println!("{attr:?}");
808        }
809    }
810
811    #[test]
812    fn test_from() {
813        let origin = Origin::IGP;
814        let attr_value = AttributeValue::from(origin);
815        assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
816
817        let aspath = AsPath::new();
818        let attr_value = AttributeValue::from(aspath);
819        assert_eq!(
820            attr_value,
821            AttributeValue::AsPath {
822                path: AsPath::new(),
823                is_as4: false
824            }
825        );
826    }
827
828    #[test]
829    fn test_well_known_mandatory_attrs() {
830        let origin_attr = AttributeValue::Origin(Origin::IGP);
831        assert_eq!(
832            origin_attr.attr_category(),
833            Some(AttributeCategory::WellKnownMandatory)
834        );
835        let as_path_attr = AttributeValue::AsPath {
836            path: AsPath::new(),
837            is_as4: false,
838        };
839        assert_eq!(
840            as_path_attr.attr_category(),
841            Some(AttributeCategory::WellKnownMandatory)
842        );
843        let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
844        assert_eq!(
845            next_hop_attr.attr_category(),
846            Some(AttributeCategory::WellKnownMandatory)
847        );
848        let local_preference_attr = AttributeValue::LocalPreference(1);
849        assert_eq!(
850            local_preference_attr.attr_category(),
851            Some(AttributeCategory::WellKnownMandatory)
852        );
853    }
854
855    #[test]
856    fn test_well_known_discretionary_attrs() {
857        let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
858        assert_eq!(
859            atomic_aggregate_attr.attr_category(),
860            Some(AttributeCategory::WellKnownDiscretionary)
861        );
862    }
863
864    #[test]
865    fn test_optional_transitive_attrs() {
866        let as_path_attr = AttributeValue::AsPath {
867            path: AsPath::new(),
868            is_as4: true,
869        };
870        assert_eq!(
871            as_path_attr.attr_category(),
872            Some(AttributeCategory::OptionalTransitive)
873        );
874        let aggregator_attr = AttributeValue::Aggregator {
875            asn: Asn::new_32bit(1),
876            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
877            is_as4: false,
878        };
879        assert_eq!(
880            aggregator_attr.attr_category(),
881            Some(AttributeCategory::OptionalTransitive)
882        );
883        let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
884        assert_eq!(
885            only_to_customer_attr.attr_category(),
886            Some(AttributeCategory::OptionalTransitive)
887        );
888        let communities_attr =
889            AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
890        assert_eq!(
891            communities_attr.attr_category(),
892            Some(AttributeCategory::OptionalTransitive)
893        );
894        let extended_communities_attr =
895            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
896        assert_eq!(
897            extended_communities_attr.attr_category(),
898            Some(AttributeCategory::OptionalTransitive)
899        );
900        let large_communities_attr =
901            AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
902        assert_eq!(
903            large_communities_attr.attr_category(),
904            Some(AttributeCategory::OptionalTransitive)
905        );
906        let aggregator_attr = AttributeValue::Aggregator {
907            asn: Asn::new_32bit(1),
908            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
909            is_as4: true,
910        };
911        assert_eq!(
912            aggregator_attr.attr_category(),
913            Some(AttributeCategory::OptionalTransitive)
914        );
915    }
916
917    #[test]
918    fn test_optional_non_transitive_attrs() {
919        let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
920        assert_eq!(
921            multi_exit_discriminator_attr.attr_category(),
922            Some(AttributeCategory::OptionalNonTransitive)
923        );
924        let originator_id_attr =
925            AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
926        assert_eq!(
927            originator_id_attr.attr_category(),
928            Some(AttributeCategory::OptionalNonTransitive)
929        );
930        let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
931        assert_eq!(
932            clusters_attr.attr_category(),
933            Some(AttributeCategory::OptionalNonTransitive)
934        );
935        let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
936            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
937        ));
938        assert_eq!(
939            mp_unreach_nlri_attr.attr_category(),
940            Some(AttributeCategory::OptionalNonTransitive)
941        );
942
943        let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
944            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
945        ));
946        assert_eq!(
947            mp_reach_nlri_attr.attr_category(),
948            Some(AttributeCategory::OptionalNonTransitive)
949        );
950    }
951
952    #[test]
953    #[cfg(feature = "serde")]
954    fn test_serde() {
955        let attributes = Attributes::from_iter(vec![
956            Attribute {
957                value: AttributeValue::Origin(Origin::IGP),
958                flag: AttrFlags::TRANSITIVE,
959            },
960            Attribute {
961                value: AttributeValue::AsPath {
962                    path: AsPath::new(),
963                    is_as4: false,
964                },
965                flag: AttrFlags::TRANSITIVE,
966            },
967        ]);
968
969        let serialized = serde_json::to_string(&attributes).unwrap();
970        let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
971
972        assert_eq!(attributes, deserialized);
973    }
974}