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::error::BgpValidationWarning;
16use crate::models::*;
17
18pub use aspath::*;
19pub use nlri::*;
20pub use origin::*;
21
22bitflags! {
23 #[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#[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 ORIGINATOR_ID = 9,
76 CLUSTER_LIST = 10,
77 CLUSTER_ID = 13,
79 MP_REACHABLE_NLRI = 14,
80 MP_UNREACHABLE_NLRI = 15,
81 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 DEVELOPMENT = 255,
101
102 #[num_enum(catch_all)]
104 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#[derive(Debug, PartialEq, Clone, Default, Eq)]
138pub struct Attributes {
139 pub(crate) inner: Vec<Attribute>,
142 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 pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
164 self.validation_warnings.push(warning);
165 }
166
167 pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
169 &self.validation_warnings
170 }
171
172 pub fn has_validation_warnings(&self) -> bool {
174 !self.validation_warnings.is_empty()
175 }
176
177 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 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 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 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 pub fn as_path(&self) -> Option<&AsPath> {
253 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 pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
285 self.into_iter()
286 }
287
288 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#[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#[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 LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
481 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
494impl From<AsPath> for AttributeValue {
496 fn from(path: AsPath) -> Self {
497 AttributeValue::AsPath {
498 path,
499 is_as4: false,
500 }
501 }
502}
503
504#[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 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 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}