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::models::*;
16
17pub use aspath::*;
18pub use nlri::*;
19pub use origin::*;
20
21bitflags! {
22    /// The high-order bit (bit 0) of the Attribute Flags octet is the
23    /// Optional bit.  It defines whether the attribute is optional (if
24    /// set to 1) or well-known (if set to 0).
25    ///
26    /// The second high-order bit (bit 1) of the Attribute Flags octet
27    /// is the Transitive bit.  It defines whether an optional
28    /// attribute is transitive (if set to 1) or non-transitive (if set
29    /// to 0).
30    ///
31    /// For well-known attributes, the Transitive bit MUST be set to 1.
32    /// (See Section 5 for a discussion of transitive attributes.)
33    ///
34    /// The third high-order bit (bit 2) of the Attribute Flags octet
35    /// is the Partial bit.  It defines whether the information
36    /// contained in the optional transitive attribute is partial (if
37    /// set to 1) or complete (if set to 0).  For well-known attributes
38    /// and for optional non-transitive attributes, the Partial bit
39    /// MUST be set to 0.
40    ///
41    /// The fourth high-order bit (bit 3) of the Attribute Flags octet
42    /// is the Extended Length bit.  It defines whether the Attribute
43    /// Length is one octet (if set to 0) or two octets (if set to 1).
44    #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
45    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46    pub struct AttrFlags: u8 {
47        const OPTIONAL   = 0b10000000;
48        const TRANSITIVE = 0b01000000;
49        const PARTIAL    = 0b00100000;
50        const EXTENDED   = 0b00010000;
51    }
52}
53
54/// Attribute types.
55///
56/// All attributes currently defined and not Unassigned or Deprecated are included here.
57/// To see the full list, check out IANA at:
58/// <https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-2>
59#[allow(non_camel_case_types)]
60#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, FromPrimitive, IntoPrimitive)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62#[repr(u8)]
63pub enum AttrType {
64    RESERVED = 0,
65    ORIGIN = 1,
66    AS_PATH = 2,
67    NEXT_HOP = 3,
68    MULTI_EXIT_DISCRIMINATOR = 4,
69    LOCAL_PREFERENCE = 5,
70    ATOMIC_AGGREGATE = 6,
71    AGGREGATOR = 7,
72    COMMUNITIES = 8,
73    /// <https://tools.ietf.org/html/rfc4456>
74    ORIGINATOR_ID = 9,
75    CLUSTER_LIST = 10,
76    /// <https://tools.ietf.org/html/rfc4760>
77    CLUSTER_ID = 13,
78    MP_REACHABLE_NLRI = 14,
79    MP_UNREACHABLE_NLRI = 15,
80    /// <https://datatracker.ietf.org/doc/html/rfc4360>
81    EXTENDED_COMMUNITIES = 16,
82    AS4_PATH = 17,
83    AS4_AGGREGATOR = 18,
84    PMSI_TUNNEL = 22,
85    TUNNEL_ENCAPSULATION = 23,
86    TRAFFIC_ENGINEERING = 24,
87    IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
88    AIGP = 26,
89    PE_DISTINGUISHER_LABELS = 27,
90    BGP_LS_ATTRIBUTE = 29,
91    LARGE_COMMUNITIES = 32,
92    BGPSEC_PATH = 33,
93    ONLY_TO_CUSTOMER = 35,
94    SFP_ATTRIBUTE = 37,
95    BFD_DISCRIMINATOR = 38,
96    BGP_PREFIX_SID = 40,
97    ATTR_SET = 128,
98    /// <https://datatracker.ietf.org/doc/html/rfc2042>
99    DEVELOPMENT = 255,
100
101    /// Catch all for any unknown attribute types
102    #[num_enum(catch_all)]
103    // We have to explicitly assign this variant a number, otherwise the compiler will attempt to
104    // assign it to 256 (previous + 1) and overflow the type.
105    Unknown(u8) = 254,
106}
107
108impl PartialOrd for AttrType {
109    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
110        Some(self.cmp(other))
111    }
112}
113
114impl Ord for AttrType {
115    fn cmp(&self, other: &Self) -> Ordering {
116        u8::from(*self).cmp(&u8::from(*other))
117    }
118}
119
120pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
121    match attr_type {
122        11 => Some("DPA"),
123        12 => Some("ADVERTISER"),
124        13 => Some("RCID_PATH"),
125        19 => Some("SAFI Specific Attribute"),
126        20 => Some("Connector Attribute"),
127        21 => Some("AS_PATHLIMIT"),
128        28 => Some("BGP Entropy Label Capability"),
129        30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
130
131        _ => None,
132    }
133}
134
135/// Convenience wrapper for a list of attributes
136#[derive(Debug, PartialEq, Eq, Clone, Default)]
137pub struct Attributes {
138    // Black box type to allow for later changes/optimizations. The most common attributes could be
139    // added as fields to allow for easier lookup.
140    pub(crate) inner: Vec<Attribute>,
141}
142
143impl Attributes {
144    pub fn has_attr(&self, ty: AttrType) -> bool {
145        self.inner.iter().any(|x| x.value.attr_type() == ty)
146    }
147
148    pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
149        self.inner
150            .iter()
151            .find(|x| x.value.attr_type() == ty)
152            .cloned()
153    }
154
155    pub fn add_attr(&mut self, attr: Attribute) {
156        self.inner.push(attr);
157    }
158
159    /// Get the `ORIGIN` attribute. In the event that this attribute is not present,
160    /// [Origin::INCOMPLETE] will be returned instead.
161    pub fn origin(&self) -> Origin {
162        self.inner
163            .iter()
164            .find_map(|x| match &x.value {
165                AttributeValue::Origin(x) => Some(*x),
166                _ => None,
167            })
168            .unwrap_or(Origin::INCOMPLETE)
169    }
170
171    /// Get the `ORIGINATOR_ID` attribute if present.
172    pub fn origin_id(&self) -> Option<BgpIdentifier> {
173        self.inner.iter().find_map(|x| match &x.value {
174            AttributeValue::OriginatorId(x) => Some(*x),
175            _ => None,
176        })
177    }
178
179    /// Get the `NEXT_HOP` attribute if present.
180    ///
181    /// **Note**: Even when this attribute is not present, the next hop address may still be
182    /// attainable from the `MP_REACH_NLRI` attribute.
183    pub fn next_hop(&self) -> Option<IpAddr> {
184        self.inner.iter().find_map(|x| match &x.value {
185            AttributeValue::NextHop(x) => Some(*x),
186            _ => None,
187        })
188    }
189
190    pub fn multi_exit_discriminator(&self) -> Option<u32> {
191        self.inner.iter().find_map(|x| match &x.value {
192            AttributeValue::MultiExitDiscriminator(x) => Some(*x),
193            _ => None,
194        })
195    }
196
197    pub fn local_preference(&self) -> Option<u32> {
198        self.inner.iter().find_map(|x| match &x.value {
199            AttributeValue::LocalPreference(x) => Some(*x),
200            _ => None,
201        })
202    }
203
204    pub fn only_to_customer(&self) -> Option<Asn> {
205        self.inner.iter().find_map(|x| match &x.value {
206            AttributeValue::OnlyToCustomer(x) => Some(*x),
207            _ => None,
208        })
209    }
210
211    pub fn atomic_aggregate(&self) -> bool {
212        self.inner
213            .iter()
214            .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
215    }
216
217    pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
218        // Begin searching at the end of the attributes to increase the odds of finding an AS4
219        // attribute first.
220        self.inner.iter().rev().find_map(|x| match &x.value {
221            AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
222            _ => None,
223        })
224    }
225
226    pub fn clusters(&self) -> Option<&[u32]> {
227        self.inner.iter().find_map(|x| match &x.value {
228            AttributeValue::Clusters(x) => Some(x.as_ref()),
229            _ => None,
230        })
231    }
232
233    // These implementations are horribly inefficient, but they were super easy to write and use
234    pub fn as_path(&self) -> Option<&AsPath> {
235        // Begin searching at the end of the attributes to increase the odds of finding an AS4
236        // attribute first.
237        self.inner.iter().rev().find_map(|x| match &x.value {
238            AttributeValue::AsPath { path, .. } => Some(path),
239            _ => None,
240        })
241    }
242
243    pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
244        self.inner.iter().find_map(|x| match &x.value {
245            AttributeValue::MpReachNlri(x) => Some(x),
246            _ => None,
247        })
248    }
249
250    pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
251        self.inner.iter().find_map(|x| match &x.value {
252            AttributeValue::MpUnreachNlri(x) => Some(x),
253            _ => None,
254        })
255    }
256
257    pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
258        MetaCommunitiesIter {
259            attributes: &self.inner,
260            index: 0,
261        }
262    }
263
264    /// Get an iterator over the held [AttributeValue]s. If you also need attribute flags, consider
265    /// using [Attributes::into_attributes_iter] instead.
266    pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
267        self.into_iter()
268    }
269
270    /// Get an iterator over the held [Attribute]s. If you do no not need attribute flags, consider
271    /// using [Attributes::iter] instead.
272    pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
273        self.inner.into_iter()
274    }
275}
276
277pub struct MetaCommunitiesIter<'a> {
278    attributes: &'a [Attribute],
279    index: usize,
280}
281
282impl Iterator for MetaCommunitiesIter<'_> {
283    type Item = MetaCommunity;
284
285    fn next(&mut self) -> Option<Self::Item> {
286        loop {
287            match &self.attributes.first()?.value {
288                AttributeValue::Communities(x) if self.index < x.len() => {
289                    self.index += 1;
290                    return Some(MetaCommunity::Plain(x[self.index - 1]));
291                }
292                AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
293                    self.index += 1;
294                    return Some(MetaCommunity::Extended(x[self.index - 1]));
295                }
296                AttributeValue::LargeCommunities(x) if self.index < x.len() => {
297                    self.index += 1;
298                    return Some(MetaCommunity::Large(x[self.index - 1]));
299                }
300                _ => {
301                    self.attributes = &self.attributes[1..];
302                    self.index = 0;
303                }
304            }
305        }
306    }
307}
308
309impl FromIterator<Attribute> for Attributes {
310    fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
311        Attributes {
312            inner: iter.into_iter().collect(),
313        }
314    }
315}
316
317impl From<Vec<Attribute>> for Attributes {
318    fn from(value: Vec<Attribute>) -> Self {
319        Attributes { inner: value }
320    }
321}
322
323impl Extend<Attribute> for Attributes {
324    fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
325        self.inner.extend(iter)
326    }
327}
328
329impl Extend<AttributeValue> for Attributes {
330    fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
331        self.extend(iter.into_iter().map(Attribute::from))
332    }
333}
334
335impl FromIterator<AttributeValue> for Attributes {
336    fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
337        Attributes {
338            inner: iter
339                .into_iter()
340                .map(|value| Attribute {
341                    value,
342                    flag: AttrFlags::empty(),
343                })
344                .collect(),
345        }
346    }
347}
348
349impl IntoIterator for Attributes {
350    type Item = AttributeValue;
351    type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
352
353    fn into_iter(self) -> Self::IntoIter {
354        self.inner.into_iter().map(|x| x.value)
355    }
356}
357
358impl<'a> IntoIterator for &'a Attributes {
359    type Item = &'a AttributeValue;
360    type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
361
362    fn into_iter(self) -> Self::IntoIter {
363        self.inner.iter().map(|x| &x.value)
364    }
365}
366
367#[cfg(feature = "serde")]
368mod serde_impl {
369    use super::*;
370    use serde::{Deserialize, Deserializer, Serialize, Serializer};
371
372    impl Serialize for Attributes {
373        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
374        where
375            S: Serializer,
376        {
377            self.inner.serialize(serializer)
378        }
379    }
380
381    impl<'de> Deserialize<'de> for Attributes {
382        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
383        where
384            D: Deserializer<'de>,
385        {
386            Ok(Attributes {
387                inner: <Vec<Attribute>>::deserialize(deserializer)?,
388            })
389        }
390    }
391}
392
393/// BGP Attribute struct with attribute value and flag
394#[derive(Debug, PartialEq, Clone, Eq)]
395#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
396pub struct Attribute {
397    pub value: AttributeValue,
398    pub flag: AttrFlags,
399}
400
401impl Attribute {
402    pub const fn is_optional(&self) -> bool {
403        self.flag.contains(AttrFlags::OPTIONAL)
404    }
405
406    pub const fn is_transitive(&self) -> bool {
407        self.flag.contains(AttrFlags::TRANSITIVE)
408    }
409
410    pub const fn is_partial(&self) -> bool {
411        self.flag.contains(AttrFlags::PARTIAL)
412    }
413
414    pub const fn is_extended(&self) -> bool {
415        self.flag.contains(AttrFlags::EXTENDED)
416    }
417}
418
419impl From<AttributeValue> for Attribute {
420    fn from(value: AttributeValue) -> Self {
421        Attribute {
422            flag: value.default_flags(),
423            value,
424        }
425    }
426}
427
428/// The `AttributeValue` enum represents different kinds of Attribute values.
429#[derive(Debug, PartialEq, Clone, Eq)]
430#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
431pub enum AttributeValue {
432    Origin(Origin),
433    AsPath {
434        path: AsPath,
435        is_as4: bool,
436    },
437    NextHop(IpAddr),
438    MultiExitDiscriminator(u32),
439    LocalPreference(u32),
440    OnlyToCustomer(Asn),
441    AtomicAggregate,
442    Aggregator {
443        asn: Asn,
444        id: BgpIdentifier,
445        is_as4: bool,
446    },
447    Communities(Vec<Community>),
448    ExtendedCommunities(Vec<ExtendedCommunity>),
449    Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
450    LargeCommunities(Vec<LargeCommunity>),
451    OriginatorId(BgpIdentifier),
452    Clusters(Vec<u32>),
453    MpReachNlri(Nlri),
454    MpUnreachNlri(Nlri),
455    Development(Vec<u8>),
456    Deprecated(AttrRaw),
457    Unknown(AttrRaw),
458}
459
460impl From<Origin> for AttributeValue {
461    fn from(value: Origin) -> Self {
462        AttributeValue::Origin(value)
463    }
464}
465
466/// Defaults to using `AS_PATH` (as opposed to `AS4_PATH`) when choosing attribute type.
467impl From<AsPath> for AttributeValue {
468    fn from(path: AsPath) -> Self {
469        AttributeValue::AsPath {
470            path,
471            is_as4: false,
472        }
473    }
474}
475
476/// Category of an attribute.
477///
478/// <https://datatracker.ietf.org/doc/html/rfc4271#section-5>
479#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
480#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
481pub enum AttributeCategory {
482    WellKnownMandatory,
483    WellKnownDiscretionary,
484    OptionalTransitive,
485    OptionalNonTransitive,
486}
487
488impl AttributeValue {
489    pub const fn attr_type(&self) -> AttrType {
490        match self {
491            AttributeValue::Origin(_) => AttrType::ORIGIN,
492            AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
493            AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
494            AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
495            AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
496            AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
497            AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
498            AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
499            AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
500            AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
501            AttributeValue::Communities(_) => AttrType::COMMUNITIES,
502            AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
503            AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
504                AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
505            }
506            AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
507            AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
508            AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
509            AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
510            AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
511            AttributeValue::Development(_) => AttrType::DEVELOPMENT,
512            AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => x.attr_type,
513        }
514    }
515
516    pub fn attr_category(&self) -> Option<AttributeCategory> {
517        use AttributeCategory::*;
518
519        match self {
520            AttributeValue::Origin(_) => Some(WellKnownMandatory),
521            AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
522            AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
523            AttributeValue::NextHop(_) => Some(WellKnownMandatory),
524            AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
525            // If we receive this attribute we must be in IBGP so it is required
526            AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
527            AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
528            AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
529            AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
530            AttributeValue::Communities(_) => Some(OptionalTransitive),
531            AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
532            AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
533            AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
534            AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
535            AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
536            AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
537            _ => None,
538        }
539    }
540
541    /// Get flags based on the attribute type. The [AttrFlags::EXTENDED] is not taken into account
542    /// when determining the correct flags.
543    pub fn default_flags(&self) -> AttrFlags {
544        match self.attr_category() {
545            None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
546            Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
547            Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
548            Some(AttributeCategory::OptionalTransitive) => {
549                AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
550            }
551            Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
552        }
553    }
554}
555
556#[derive(Debug, PartialEq, Clone, Eq)]
557#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
558pub struct AttrRaw {
559    pub attr_type: AttrType,
560    pub bytes: Vec<u8>,
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566    use std::net::Ipv4Addr;
567    use std::str::FromStr;
568
569    #[test]
570    fn test_attr_type() {
571        let attr_value = AttributeValue::Origin(Origin::IGP);
572        assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
573    }
574
575    #[test]
576    fn test_attr_category() {
577        let attr_value = AttributeValue::Origin(Origin::IGP);
578        let category = attr_value.attr_category().unwrap();
579        assert_eq!(category, AttributeCategory::WellKnownMandatory);
580    }
581
582    #[test]
583    fn test_default_flags() {
584        let attr_value = AttributeValue::Origin(Origin::IGP);
585        let flags = attr_value.default_flags();
586        assert_eq!(flags, AttrFlags::TRANSITIVE);
587    }
588
589    #[test]
590    fn test_get_attr() {
591        let attribute = Attribute {
592            value: AttributeValue::Origin(Origin::IGP),
593            flag: AttrFlags::TRANSITIVE,
594        };
595
596        let mut attributes = Attributes::default();
597        attributes.add_attr(attribute.clone());
598
599        assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
600    }
601
602    #[test]
603    fn test_has_attr() {
604        let attribute = Attribute {
605            value: AttributeValue::Origin(Origin::IGP),
606            flag: AttrFlags::TRANSITIVE,
607        };
608
609        let mut attributes = Attributes::default();
610        attributes.add_attr(attribute);
611
612        assert!(attributes.has_attr(AttrType::ORIGIN));
613    }
614
615    #[test]
616    fn test_getting_all_attributes() {
617        let mut attributes = Attributes::default();
618        attributes.add_attr(Attribute {
619            value: AttributeValue::Origin(Origin::IGP),
620            flag: AttrFlags::TRANSITIVE,
621        });
622        attributes.add_attr(Attribute {
623            value: AttributeValue::AsPath {
624                path: AsPath::new(),
625                is_as4: false,
626            },
627            flag: AttrFlags::TRANSITIVE,
628        });
629        attributes.add_attr(Attribute {
630            value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
631            flag: AttrFlags::TRANSITIVE,
632        });
633        attributes.add_attr(Attribute {
634            value: AttributeValue::MultiExitDiscriminator(1),
635            flag: AttrFlags::TRANSITIVE,
636        });
637
638        attributes.add_attr(Attribute {
639            value: AttributeValue::LocalPreference(1),
640            flag: AttrFlags::TRANSITIVE,
641        });
642        attributes.add_attr(Attribute {
643            value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
644            flag: AttrFlags::TRANSITIVE,
645        });
646        attributes.add_attr(Attribute {
647            value: AttributeValue::AtomicAggregate,
648            flag: AttrFlags::TRANSITIVE,
649        });
650        attributes.add_attr(Attribute {
651            value: AttributeValue::Clusters(vec![1, 2, 3]),
652            flag: AttrFlags::TRANSITIVE,
653        });
654        attributes.add_attr(Attribute {
655            value: AttributeValue::Aggregator {
656                asn: Asn::new_32bit(1),
657                id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
658                is_as4: false,
659            },
660            flag: AttrFlags::TRANSITIVE,
661        });
662        attributes.add_attr(Attribute {
663            value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
664            flag: AttrFlags::TRANSITIVE,
665        });
666
667        assert_eq!(attributes.origin(), Origin::IGP);
668        assert_eq!(attributes.as_path(), Some(&AsPath::new()));
669        assert_eq!(
670            attributes.next_hop(),
671            Some(IpAddr::from_str("10.0.0.0").unwrap())
672        );
673        assert_eq!(attributes.multi_exit_discriminator(), Some(1));
674        assert_eq!(attributes.local_preference(), Some(1));
675        assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
676        assert!(attributes.atomic_aggregate());
677        assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
678        assert_eq!(
679            attributes.aggregator(),
680            Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
681        );
682        assert_eq!(
683            attributes.origin_id(),
684            Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
685        );
686
687        let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
688        assert!(aspath_attr.is_transitive());
689        assert!(!aspath_attr.is_extended());
690        assert!(!aspath_attr.is_partial());
691        assert!(!aspath_attr.is_optional());
692
693        for attr in attributes.iter() {
694            println!("{:?}", attr);
695        }
696    }
697
698    #[test]
699    fn test_from() {
700        let origin = Origin::IGP;
701        let attr_value = AttributeValue::from(origin);
702        assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
703
704        let aspath = AsPath::new();
705        let attr_value = AttributeValue::from(aspath);
706        assert_eq!(
707            attr_value,
708            AttributeValue::AsPath {
709                path: AsPath::new(),
710                is_as4: false
711            }
712        );
713    }
714
715    #[test]
716    fn test_well_known_mandatory_attrs() {
717        let origin_attr = AttributeValue::Origin(Origin::IGP);
718        assert_eq!(
719            origin_attr.attr_category(),
720            Some(AttributeCategory::WellKnownMandatory)
721        );
722        let as_path_attr = AttributeValue::AsPath {
723            path: AsPath::new(),
724            is_as4: false,
725        };
726        assert_eq!(
727            as_path_attr.attr_category(),
728            Some(AttributeCategory::WellKnownMandatory)
729        );
730        let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
731        assert_eq!(
732            next_hop_attr.attr_category(),
733            Some(AttributeCategory::WellKnownMandatory)
734        );
735        let local_preference_attr = AttributeValue::LocalPreference(1);
736        assert_eq!(
737            local_preference_attr.attr_category(),
738            Some(AttributeCategory::WellKnownMandatory)
739        );
740    }
741
742    #[test]
743    fn test_well_known_discretionary_attrs() {
744        let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
745        assert_eq!(
746            atomic_aggregate_attr.attr_category(),
747            Some(AttributeCategory::WellKnownDiscretionary)
748        );
749    }
750
751    #[test]
752    fn test_optional_transitive_attrs() {
753        let as_path_attr = AttributeValue::AsPath {
754            path: AsPath::new(),
755            is_as4: true,
756        };
757        assert_eq!(
758            as_path_attr.attr_category(),
759            Some(AttributeCategory::OptionalTransitive)
760        );
761        let aggregator_attr = AttributeValue::Aggregator {
762            asn: Asn::new_32bit(1),
763            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
764            is_as4: false,
765        };
766        assert_eq!(
767            aggregator_attr.attr_category(),
768            Some(AttributeCategory::OptionalTransitive)
769        );
770        let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
771        assert_eq!(
772            only_to_customer_attr.attr_category(),
773            Some(AttributeCategory::OptionalTransitive)
774        );
775        let communities_attr =
776            AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
777        assert_eq!(
778            communities_attr.attr_category(),
779            Some(AttributeCategory::OptionalTransitive)
780        );
781        let extended_communities_attr =
782            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
783        assert_eq!(
784            extended_communities_attr.attr_category(),
785            Some(AttributeCategory::OptionalTransitive)
786        );
787        let large_communities_attr =
788            AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
789        assert_eq!(
790            large_communities_attr.attr_category(),
791            Some(AttributeCategory::OptionalTransitive)
792        );
793        let aggregator_attr = AttributeValue::Aggregator {
794            asn: Asn::new_32bit(1),
795            id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
796            is_as4: true,
797        };
798        assert_eq!(
799            aggregator_attr.attr_category(),
800            Some(AttributeCategory::OptionalTransitive)
801        );
802    }
803
804    #[test]
805    fn test_optional_non_transitive_attrs() {
806        let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
807        assert_eq!(
808            multi_exit_discriminator_attr.attr_category(),
809            Some(AttributeCategory::OptionalNonTransitive)
810        );
811        let originator_id_attr =
812            AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
813        assert_eq!(
814            originator_id_attr.attr_category(),
815            Some(AttributeCategory::OptionalNonTransitive)
816        );
817        let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
818        assert_eq!(
819            clusters_attr.attr_category(),
820            Some(AttributeCategory::OptionalNonTransitive)
821        );
822        let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
823            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
824        ));
825        assert_eq!(
826            mp_unreach_nlri_attr.attr_category(),
827            Some(AttributeCategory::OptionalNonTransitive)
828        );
829
830        let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
831            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
832        ));
833        assert_eq!(
834            mp_reach_nlri_attr.attr_category(),
835            Some(AttributeCategory::OptionalNonTransitive)
836        );
837    }
838
839    #[test]
840    #[cfg(feature = "serde")]
841    fn test_serde() {
842        let attributes = Attributes::from_iter(vec![
843            Attribute {
844                value: AttributeValue::Origin(Origin::IGP),
845                flag: AttrFlags::TRANSITIVE,
846            },
847            Attribute {
848                value: AttributeValue::AsPath {
849                    path: AsPath::new(),
850                    is_as4: false,
851                },
852                flag: AttrFlags::TRANSITIVE,
853            },
854        ]);
855
856        let serialized = serde_json::to_string(&attributes).unwrap();
857        let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
858
859        assert_eq!(attributes, deserialized);
860    }
861}