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