1mod 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 #[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#[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 ORIGINATOR_ID = 9,
75 CLUSTER_LIST = 10,
76 CLUSTER_ID = 13,
78 MP_REACHABLE_NLRI = 14,
79 MP_UNREACHABLE_NLRI = 15,
80 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 DEVELOPMENT = 255,
100
101 #[num_enum(catch_all)]
103 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#[derive(Debug, PartialEq, Eq, Clone, Default)]
137pub struct Attributes {
138 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 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 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 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 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 pub fn as_path(&self) -> Option<&AsPath> {
235 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 pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
267 self.into_iter()
268 }
269
270 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#[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#[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
466impl From<AsPath> for AttributeValue {
468 fn from(path: AsPath) -> Self {
469 AttributeValue::AsPath {
470 path,
471 is_as4: false,
472 }
473 }
474}
475
476#[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 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 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}