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
361                .into_iter()
362                .map(|value| Attribute {
363                    value,
364                    flag: AttrFlags::empty(),
365                })
366                .collect(),
367            validation_warnings: Vec::new(),
368        }
369    }
370}
371
372impl IntoIterator for Attributes {
373    type Item = AttributeValue;
374    type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
375
376    fn into_iter(self) -> Self::IntoIter {
377        self.inner.into_iter().map(|x| x.value)
378    }
379}
380
381impl<'a> IntoIterator for &'a Attributes {
382    type Item = &'a AttributeValue;
383    type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
384
385    fn into_iter(self) -> Self::IntoIter {
386        self.inner.iter().map(|x| &x.value)
387    }
388}
389
390#[cfg(feature = "serde")]
391mod serde_impl {
392    use super::*;
393    use serde::{Deserialize, Deserializer, Serialize, Serializer};
394
395    impl Serialize for Attributes {
396        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
397        where
398            S: Serializer,
399        {
400            self.inner.serialize(serializer)
401        }
402    }
403
404    impl<'de> Deserialize<'de> for Attributes {
405        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
406        where
407            D: Deserializer<'de>,
408        {
409            Ok(Attributes {
410                inner: <Vec<Attribute>>::deserialize(deserializer)?,
411                validation_warnings: Vec::new(),
412            })
413        }
414    }
415}
416
417/// BGP Attribute struct with attribute value and flag
418#[derive(Debug, PartialEq, Clone, Eq)]
419#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
420pub struct Attribute {
421    pub value: AttributeValue,
422    pub flag: AttrFlags,
423}
424
425impl Attribute {
426    pub const fn is_optional(&self) -> bool {
427        self.flag.contains(AttrFlags::OPTIONAL)
428    }
429
430    pub const fn is_transitive(&self) -> bool {
431        self.flag.contains(AttrFlags::TRANSITIVE)
432    }
433
434    pub const fn is_partial(&self) -> bool {
435        self.flag.contains(AttrFlags::PARTIAL)
436    }
437
438    pub const fn is_extended(&self) -> bool {
439        self.flag.contains(AttrFlags::EXTENDED)
440    }
441}
442
443impl From<AttributeValue> for Attribute {
444    fn from(value: AttributeValue) -> Self {
445        Attribute {
446            flag: value.default_flags(),
447            value,
448        }
449    }
450}
451
452/// The `AttributeValue` enum represents different kinds of Attribute values.
453#[derive(Debug, PartialEq, Clone, Eq)]
454#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
455pub enum AttributeValue {
456    Origin(Origin),
457    AsPath {
458        path: AsPath,
459        is_as4: bool,
460    },
461    NextHop(IpAddr),
462    MultiExitDiscriminator(u32),
463    LocalPreference(u32),
464    OnlyToCustomer(Asn),
465    AtomicAggregate,
466    Aggregator {
467        asn: Asn,
468        id: BgpIdentifier,
469        is_as4: bool,
470    },
471    Communities(Vec<Community>),
472    ExtendedCommunities(Vec<ExtendedCommunity>),
473    Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
474    LargeCommunities(Vec<LargeCommunity>),
475    OriginatorId(BgpIdentifier),
476    Clusters(Vec<u32>),
477    MpReachNlri(Nlri),
478    MpUnreachNlri(Nlri),
479    /// BGP Link-State attribute - RFC 7752
480    LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
481    /// BGP Tunnel Encapsulation attribute - RFC 9012
482    TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
483    Development(Vec<u8>),
484    Deprecated(AttrRaw),
485    Unknown(AttrRaw),
486}
487
488impl From<Origin> for AttributeValue {
489    fn from(value: Origin) -> Self {
490        AttributeValue::Origin(value)
491    }
492}
493
494/// Defaults to using `AS_PATH` (as opposed to `AS4_PATH`) when choosing attribute type.
495impl From<AsPath> for AttributeValue {
496    fn from(path: AsPath) -> Self {
497        AttributeValue::AsPath {
498            path,
499            is_as4: false,
500        }
501    }
502}
503
504/// Category of an attribute.
505///
506/// <https://datatracker.ietf.org/doc/html/rfc4271#section-5>
507#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
508#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
509pub enum AttributeCategory {
510    WellKnownMandatory,
511    WellKnownDiscretionary,
512    OptionalTransitive,
513    OptionalNonTransitive,
514}
515
516impl AttributeValue {
517    pub const fn attr_type(&self) -> AttrType {
518        match self {
519            AttributeValue::Origin(_) => AttrType::ORIGIN,
520            AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
521            AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
522            AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
523            AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
524            AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
525            AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
526            AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
527            AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
528            AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
529            AttributeValue::Communities(_) => AttrType::COMMUNITIES,
530            AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
531            AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
532                AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
533            }
534            AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
535            AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
536            AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
537            AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
538            AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
539            AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
540            AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
541            AttributeValue::Development(_) => AttrType::DEVELOPMENT,
542            AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => x.attr_type,
543        }
544    }
545
546    pub fn attr_category(&self) -> Option<AttributeCategory> {
547        use AttributeCategory::*;
548
549        match self {
550            AttributeValue::Origin(_) => Some(WellKnownMandatory),
551            AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
552            AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
553            AttributeValue::NextHop(_) => Some(WellKnownMandatory),
554            AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
555            // If we receive this attribute we must be in IBGP so it is required
556            AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
557            AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
558            AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
559            AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
560            AttributeValue::Communities(_) => Some(OptionalTransitive),
561            AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
562            AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
563            AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
564            AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
565            AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
566            AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
567            AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
568            _ => None,
569        }
570    }
571
572    /// Get flags based on the attribute type. The [AttrFlags::EXTENDED] is not taken into account
573    /// when determining the correct flags.
574    pub fn default_flags(&self) -> AttrFlags {
575        match self.attr_category() {
576            None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
577            Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
578            Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
579            Some(AttributeCategory::OptionalTransitive) => {
580                AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
581            }
582            Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
583        }
584    }
585}
586
587#[derive(Debug, PartialEq, Clone, Eq)]
588#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
589pub struct AttrRaw {
590    pub attr_type: AttrType,
591    pub bytes: Vec<u8>,
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597    use std::net::Ipv4Addr;
598    use std::str::FromStr;
599
600    #[test]
601    fn test_attr_type() {
602        let attr_value = AttributeValue::Origin(Origin::IGP);
603        assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
604    }
605
606    #[test]
607    fn test_attr_category() {
608        let attr_value = AttributeValue::Origin(Origin::IGP);
609        let category = attr_value.attr_category().unwrap();
610        assert_eq!(category, AttributeCategory::WellKnownMandatory);
611    }
612
613    #[test]
614    fn test_default_flags() {
615        let attr_value = AttributeValue::Origin(Origin::IGP);
616        let flags = attr_value.default_flags();
617        assert_eq!(flags, AttrFlags::TRANSITIVE);
618    }
619
620    #[test]
621    fn test_get_attr() {
622        let attribute = Attribute {
623            value: AttributeValue::Origin(Origin::IGP),
624            flag: AttrFlags::TRANSITIVE,
625        };
626
627        let mut attributes = Attributes::default();
628        attributes.add_attr(attribute.clone());
629
630        assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
631    }
632
633    #[test]
634    fn test_has_attr() {
635        let attribute = Attribute {
636            value: AttributeValue::Origin(Origin::IGP),
637            flag: AttrFlags::TRANSITIVE,
638        };
639
640        let mut attributes = Attributes::default();
641        attributes.add_attr(attribute);
642
643        assert!(attributes.has_attr(AttrType::ORIGIN));
644    }
645
646    #[test]
647    fn test_getting_all_attributes() {
648        let mut attributes = Attributes::default();
649        attributes.add_attr(Attribute {
650            value: AttributeValue::Origin(Origin::IGP),
651            flag: AttrFlags::TRANSITIVE,
652        });
653        attributes.add_attr(Attribute {
654            value: AttributeValue::AsPath {
655                path: AsPath::new(),
656                is_as4: false,
657            },
658            flag: AttrFlags::TRANSITIVE,
659        });
660        attributes.add_attr(Attribute {
661            value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
662            flag: AttrFlags::TRANSITIVE,
663        });
664        attributes.add_attr(Attribute {
665            value: AttributeValue::MultiExitDiscriminator(1),
666            flag: AttrFlags::TRANSITIVE,
667        });
668
669        attributes.add_attr(Attribute {
670            value: AttributeValue::LocalPreference(1),
671            flag: AttrFlags::TRANSITIVE,
672        });
673        attributes.add_attr(Attribute {
674            value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
675            flag: AttrFlags::TRANSITIVE,
676        });
677        attributes.add_attr(Attribute {
678            value: AttributeValue::AtomicAggregate,
679            flag: AttrFlags::TRANSITIVE,
680        });
681        attributes.add_attr(Attribute {
682            value: AttributeValue::Clusters(vec![1, 2, 3]),
683            flag: AttrFlags::TRANSITIVE,
684        });
685        attributes.add_attr(Attribute {
686            value: AttributeValue::Aggregator {
687                asn: Asn::new_32bit(1),
688                id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
689                is_as4: false,
690            },
691            flag: AttrFlags::TRANSITIVE,
692        });
693        attributes.add_attr(Attribute {
694            value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
695            flag: AttrFlags::TRANSITIVE,
696        });
697
698        assert_eq!(attributes.origin(), Origin::IGP);
699        assert_eq!(attributes.as_path(), Some(&AsPath::new()));
700        assert_eq!(
701            attributes.next_hop(),
702            Some(IpAddr::from_str("10.0.0.0").unwrap())
703        );
704        assert_eq!(attributes.multi_exit_discriminator(), Some(1));
705        assert_eq!(attributes.local_preference(), Some(1));
706        assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
707        assert!(attributes.atomic_aggregate());
708        assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
709        assert_eq!(
710            attributes.aggregator(),
711            Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
712        );
713        assert_eq!(
714            attributes.origin_id(),
715            Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
716        );
717
718        let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
719        assert!(aspath_attr.is_transitive());
720        assert!(!aspath_attr.is_extended());
721        assert!(!aspath_attr.is_partial());
722        assert!(!aspath_attr.is_optional());
723
724        for attr in attributes.iter() {
725            println!("{attr:?}");
726        }
727    }
728
729    #[test]
730    fn test_from() {
731        let origin = Origin::IGP;
732        let attr_value = AttributeValue::from(origin);
733        assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
734
735        let aspath = AsPath::new();
736        let attr_value = AttributeValue::from(aspath);
737        assert_eq!(
738            attr_value,
739            AttributeValue::AsPath {
740                path: AsPath::new(),
741                is_as4: false
742            }
743        );
744    }
745
746    #[test]
747    fn test_well_known_mandatory_attrs() {
748        let origin_attr = AttributeValue::Origin(Origin::IGP);
749        assert_eq!(
750            origin_attr.attr_category(),
751            Some(AttributeCategory::WellKnownMandatory)
752        );
753        let as_path_attr = AttributeValue::AsPath {
754            path: AsPath::new(),
755            is_as4: false,
756        };
757        assert_eq!(
758            as_path_attr.attr_category(),
759            Some(AttributeCategory::WellKnownMandatory)
760        );
761        let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
762        assert_eq!(
763            next_hop_attr.attr_category(),
764            Some(AttributeCategory::WellKnownMandatory)
765        );
766        let local_preference_attr = AttributeValue::LocalPreference(1);
767        assert_eq!(
768            local_preference_attr.attr_category(),
769            Some(AttributeCategory::WellKnownMandatory)
770        );
771    }
772
773    #[test]
774    fn test_well_known_discretionary_attrs() {
775        let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
776        assert_eq!(
777            atomic_aggregate_attr.attr_category(),
778            Some(AttributeCategory::WellKnownDiscretionary)
779        );
780    }
781
782    #[test]
783    fn test_optional_transitive_attrs() {
784        let as_path_attr = AttributeValue::AsPath {
785            path: AsPath::new(),
786            is_as4: true,
787        };
788        assert_eq!(
789            as_path_attr.attr_category(),
790            Some(AttributeCategory::OptionalTransitive)
791        );
792        let aggregator_attr = AttributeValue::Aggregator {
793            asn: Asn::new_32bit(1),
794            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
795            is_as4: false,
796        };
797        assert_eq!(
798            aggregator_attr.attr_category(),
799            Some(AttributeCategory::OptionalTransitive)
800        );
801        let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
802        assert_eq!(
803            only_to_customer_attr.attr_category(),
804            Some(AttributeCategory::OptionalTransitive)
805        );
806        let communities_attr =
807            AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
808        assert_eq!(
809            communities_attr.attr_category(),
810            Some(AttributeCategory::OptionalTransitive)
811        );
812        let extended_communities_attr =
813            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
814        assert_eq!(
815            extended_communities_attr.attr_category(),
816            Some(AttributeCategory::OptionalTransitive)
817        );
818        let large_communities_attr =
819            AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
820        assert_eq!(
821            large_communities_attr.attr_category(),
822            Some(AttributeCategory::OptionalTransitive)
823        );
824        let aggregator_attr = AttributeValue::Aggregator {
825            asn: Asn::new_32bit(1),
826            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
827            is_as4: true,
828        };
829        assert_eq!(
830            aggregator_attr.attr_category(),
831            Some(AttributeCategory::OptionalTransitive)
832        );
833    }
834
835    #[test]
836    fn test_optional_non_transitive_attrs() {
837        let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
838        assert_eq!(
839            multi_exit_discriminator_attr.attr_category(),
840            Some(AttributeCategory::OptionalNonTransitive)
841        );
842        let originator_id_attr =
843            AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
844        assert_eq!(
845            originator_id_attr.attr_category(),
846            Some(AttributeCategory::OptionalNonTransitive)
847        );
848        let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
849        assert_eq!(
850            clusters_attr.attr_category(),
851            Some(AttributeCategory::OptionalNonTransitive)
852        );
853        let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
854            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
855        ));
856        assert_eq!(
857            mp_unreach_nlri_attr.attr_category(),
858            Some(AttributeCategory::OptionalNonTransitive)
859        );
860
861        let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
862            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
863        ));
864        assert_eq!(
865            mp_reach_nlri_attr.attr_category(),
866            Some(AttributeCategory::OptionalNonTransitive)
867        );
868    }
869
870    #[test]
871    #[cfg(feature = "serde")]
872    fn test_serde() {
873        let attributes = Attributes::from_iter(vec![
874            Attribute {
875                value: AttributeValue::Origin(Origin::IGP),
876                flag: AttrFlags::TRANSITIVE,
877            },
878            Attribute {
879                value: AttributeValue::AsPath {
880                    path: AsPath::new(),
881                    is_as4: false,
882                },
883                flag: AttrFlags::TRANSITIVE,
884            },
885        ]);
886
887        let serialized = serde_json::to_string(&attributes).unwrap();
888        let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
889
890        assert_eq!(attributes, deserialized);
891    }
892}