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(PartialEq, Clone, Default, Eq)]
138pub struct Attributes {
139 pub(crate) inner: Vec<Attribute>,
142 pub(crate) validation_warnings: Vec<BgpValidationWarning>,
144 pub(crate) attr_mask: [u64; 4],
146}
147
148impl std::fmt::Debug for Attributes {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("Attributes")
151 .field("inner", &self.inner)
152 .field("validation_warnings", &self.validation_warnings)
153 .finish()
154 }
155}
156
157impl Attributes {
158 pub fn has_attr(&self, ty: AttrType) -> bool {
159 let attr = u8::from(ty);
160 (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
161 }
162
163 pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
164 self.inner
165 .iter()
166 .find(|x| x.value.attr_type() == ty)
167 .cloned()
168 }
169
170 pub fn add_attr(&mut self, attr: Attribute) {
171 let ty = u8::from(attr.value.attr_type());
172 self.attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
173 self.inner.push(attr);
174 }
175
176 pub fn check_mandatory_attributes(&mut self, is_announcement: bool, has_standard_nlri: bool) {
183 if !is_announcement {
184 return;
185 }
186
187 if !self.has_attr(AttrType::ORIGIN) {
189 self.validation_warnings
190 .push(BgpValidationWarning::MissingWellKnownAttribute {
191 attr_type: AttrType::ORIGIN,
192 });
193 }
194 if !self.has_attr(AttrType::AS_PATH) {
195 self.validation_warnings
196 .push(BgpValidationWarning::MissingWellKnownAttribute {
197 attr_type: AttrType::AS_PATH,
198 });
199 }
200
201 let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
205 if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
206 self.validation_warnings
207 .push(BgpValidationWarning::MissingWellKnownAttribute {
208 attr_type: AttrType::NEXT_HOP,
209 });
210 }
211 }
212
213 pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
215 self.validation_warnings.push(warning);
216 }
217
218 pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
220 &self.validation_warnings
221 }
222
223 pub fn has_validation_warnings(&self) -> bool {
225 !self.validation_warnings.is_empty()
226 }
227
228 pub fn origin(&self) -> Origin {
231 self.inner
232 .iter()
233 .find_map(|x| match &x.value {
234 AttributeValue::Origin(x) => Some(*x),
235 _ => None,
236 })
237 .unwrap_or(Origin::INCOMPLETE)
238 }
239
240 pub fn origin_id(&self) -> Option<BgpIdentifier> {
242 self.inner.iter().find_map(|x| match &x.value {
243 AttributeValue::OriginatorId(x) => Some(*x),
244 _ => None,
245 })
246 }
247
248 pub fn next_hop(&self) -> Option<IpAddr> {
253 self.inner.iter().find_map(|x| match &x.value {
254 AttributeValue::NextHop(x) => Some(*x),
255 _ => None,
256 })
257 }
258
259 pub fn multi_exit_discriminator(&self) -> Option<u32> {
260 self.inner.iter().find_map(|x| match &x.value {
261 AttributeValue::MultiExitDiscriminator(x) => Some(*x),
262 _ => None,
263 })
264 }
265
266 pub fn local_preference(&self) -> Option<u32> {
267 self.inner.iter().find_map(|x| match &x.value {
268 AttributeValue::LocalPreference(x) => Some(*x),
269 _ => None,
270 })
271 }
272
273 pub fn only_to_customer(&self) -> Option<Asn> {
274 self.inner.iter().find_map(|x| match &x.value {
275 AttributeValue::OnlyToCustomer(x) => Some(*x),
276 _ => None,
277 })
278 }
279
280 pub fn atomic_aggregate(&self) -> bool {
281 self.inner
282 .iter()
283 .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
284 }
285
286 pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
287 self.inner.iter().rev().find_map(|x| match &x.value {
290 AttributeValue::Aggregator { asn, id, .. } => Some((*asn, *id)),
291 _ => None,
292 })
293 }
294
295 pub fn clusters(&self) -> Option<&[u32]> {
296 self.inner.iter().find_map(|x| match &x.value {
297 AttributeValue::Clusters(x) => Some(x.as_ref()),
298 _ => None,
299 })
300 }
301
302 pub fn as_path(&self) -> Option<&AsPath> {
304 self.inner.iter().rev().find_map(|x| match &x.value {
307 AttributeValue::AsPath { path, .. } => Some(path),
308 _ => None,
309 })
310 }
311
312 pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
313 self.inner.iter().find_map(|x| match &x.value {
314 AttributeValue::MpReachNlri(x) => Some(x),
315 _ => None,
316 })
317 }
318
319 pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
320 self.inner.iter().find_map(|x| match &x.value {
321 AttributeValue::MpUnreachNlri(x) => Some(x),
322 _ => None,
323 })
324 }
325
326 pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
327 MetaCommunitiesIter {
328 attributes: &self.inner,
329 index: 0,
330 }
331 }
332
333 pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
336 self.into_iter()
337 }
338
339 pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
342 self.inner.into_iter()
343 }
344}
345
346pub struct MetaCommunitiesIter<'a> {
347 attributes: &'a [Attribute],
348 index: usize,
349}
350
351impl Iterator for MetaCommunitiesIter<'_> {
352 type Item = MetaCommunity;
353
354 fn next(&mut self) -> Option<Self::Item> {
355 loop {
356 match &self.attributes.first()?.value {
357 AttributeValue::Communities(x) if self.index < x.len() => {
358 self.index += 1;
359 return Some(MetaCommunity::Plain(x[self.index - 1]));
360 }
361 AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
362 self.index += 1;
363 return Some(MetaCommunity::Extended(x[self.index - 1]));
364 }
365 AttributeValue::LargeCommunities(x) if self.index < x.len() => {
366 self.index += 1;
367 return Some(MetaCommunity::Large(x[self.index - 1]));
368 }
369 _ => {
370 self.attributes = &self.attributes[1..];
371 self.index = 0;
372 }
373 }
374 }
375 }
376}
377
378fn compute_mask(inner: &[Attribute]) -> [u64; 4] {
379 let mut attr_mask = [0; 4];
380 for attr in inner {
381 let ty = u8::from(attr.value.attr_type());
382 attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
383 }
384 attr_mask
385}
386
387impl FromIterator<Attribute> for Attributes {
388 fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
389 let inner: Vec<Attribute> = iter.into_iter().collect();
390 let attr_mask = compute_mask(&inner);
391 Attributes {
392 inner,
393 validation_warnings: Vec::new(),
394 attr_mask,
395 }
396 }
397}
398
399impl From<Vec<Attribute>> for Attributes {
400 fn from(value: Vec<Attribute>) -> Self {
401 let attr_mask = compute_mask(&value);
402 Attributes {
403 inner: value,
404 validation_warnings: Vec::new(),
405 attr_mask,
406 }
407 }
408}
409
410impl Extend<Attribute> for Attributes {
411 fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
412 for attr in iter {
413 self.add_attr(attr);
414 }
415 }
416}
417
418impl Extend<AttributeValue> for Attributes {
419 fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
420 self.extend(iter.into_iter().map(Attribute::from))
421 }
422}
423
424impl FromIterator<AttributeValue> for Attributes {
425 fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
426 let inner: Vec<Attribute> = iter.into_iter().map(Attribute::from).collect();
427 let attr_mask = compute_mask(&inner);
428 Attributes {
429 inner,
430 validation_warnings: Vec::new(),
431 attr_mask,
432 }
433 }
434}
435
436impl IntoIterator for Attributes {
437 type Item = AttributeValue;
438 type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
439
440 fn into_iter(self) -> Self::IntoIter {
441 self.inner.into_iter().map(|x| x.value)
442 }
443}
444
445impl<'a> IntoIterator for &'a Attributes {
446 type Item = &'a AttributeValue;
447 type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
448
449 fn into_iter(self) -> Self::IntoIter {
450 self.inner.iter().map(|x| &x.value)
451 }
452}
453
454#[cfg(feature = "serde")]
455mod serde_impl {
456 use super::*;
457 use serde::{Deserialize, Deserializer, Serialize, Serializer};
458
459 impl Serialize for Attributes {
460 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
461 where
462 S: Serializer,
463 {
464 self.inner.serialize(serializer)
465 }
466 }
467
468 impl<'de> Deserialize<'de> for Attributes {
469 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
470 where
471 D: Deserializer<'de>,
472 {
473 let inner = <Vec<Attribute>>::deserialize(deserializer)?;
474 let attr_mask = compute_mask(&inner);
475 Ok(Attributes {
476 inner,
477 validation_warnings: Vec::new(),
478 attr_mask,
479 })
480 }
481 }
482}
483
484#[derive(Debug, PartialEq, Clone, Eq)]
486#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
487pub struct Attribute {
488 pub value: AttributeValue,
489 pub flag: AttrFlags,
490}
491
492impl Attribute {
493 pub const fn is_optional(&self) -> bool {
494 self.flag.contains(AttrFlags::OPTIONAL)
495 }
496
497 pub const fn is_transitive(&self) -> bool {
498 self.flag.contains(AttrFlags::TRANSITIVE)
499 }
500
501 pub const fn is_partial(&self) -> bool {
502 self.flag.contains(AttrFlags::PARTIAL)
503 }
504
505 pub const fn is_extended(&self) -> bool {
506 self.flag.contains(AttrFlags::EXTENDED)
507 }
508}
509
510impl From<AttributeValue> for Attribute {
511 fn from(value: AttributeValue) -> Self {
512 Attribute {
513 flag: value.default_flags(),
514 value,
515 }
516 }
517}
518
519#[derive(Debug, PartialEq, Clone, Eq)]
521#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
522pub struct AigpTlv {
523 pub tlv_type: u8,
524 pub length: u16,
525 pub value: Vec<u8>,
526}
527
528#[derive(Debug, PartialEq, Clone, Eq)]
533#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
534pub struct Aigp {
535 pub tlvs: Vec<AigpTlv>,
536}
537
538impl Aigp {
539 pub fn accumulated_metric(&self) -> Option<u64> {
541 self.tlvs
542 .iter()
543 .find(|tlv| tlv.tlv_type == 1)
544 .and_then(|tlv| {
545 if tlv.value.len() >= 8 {
546 Some(u64::from_be_bytes([
547 tlv.value[0],
548 tlv.value[1],
549 tlv.value[2],
550 tlv.value[3],
551 tlv.value[4],
552 tlv.value[5],
553 tlv.value[6],
554 tlv.value[7],
555 ]))
556 } else {
557 None
558 }
559 })
560 }
561}
562
563#[derive(Debug, PartialEq, Clone, Eq)]
571#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
572pub struct AttrSet {
573 pub origin_as: Asn,
575 pub attributes: Attributes,
577}
578
579#[derive(Debug, PartialEq, Clone, Eq)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582pub enum AttributeValue {
583 Origin(Origin),
584 AsPath {
585 path: AsPath,
586 is_as4: bool,
587 },
588 NextHop(IpAddr),
589 MultiExitDiscriminator(u32),
590 LocalPreference(u32),
591 OnlyToCustomer(Asn),
592 AtomicAggregate,
593 Aggregator {
594 asn: Asn,
595 id: BgpIdentifier,
596 is_as4: bool,
597 },
598 Communities(Vec<Community>),
599 ExtendedCommunities(Vec<ExtendedCommunity>),
600 Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
601 LargeCommunities(Vec<LargeCommunity>),
602 OriginatorId(BgpIdentifier),
603 Clusters(Vec<u32>),
604 MpReachNlri(Nlri),
605 MpUnreachNlri(Nlri),
606 LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
608 TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
610 Development(Vec<u8>),
611 Deprecated(AttrRaw),
612 Unknown(AttrRaw),
613 Aigp(Aigp),
615 AttrSet(AttrSet),
617}
618
619impl From<Origin> for AttributeValue {
620 fn from(value: Origin) -> Self {
621 AttributeValue::Origin(value)
622 }
623}
624
625impl From<AsPath> for AttributeValue {
627 fn from(path: AsPath) -> Self {
628 AttributeValue::AsPath {
629 path,
630 is_as4: false,
631 }
632 }
633}
634
635#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
639#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
640pub enum AttributeCategory {
641 WellKnownMandatory,
642 WellKnownDiscretionary,
643 OptionalTransitive,
644 OptionalNonTransitive,
645}
646
647impl AttributeValue {
648 pub const fn attr_type(&self) -> AttrType {
649 match self {
650 AttributeValue::Origin(_) => AttrType::ORIGIN,
651 AttributeValue::AsPath { is_as4: false, .. } => AttrType::AS_PATH,
652 AttributeValue::AsPath { is_as4: true, .. } => AttrType::AS4_PATH,
653 AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
654 AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
655 AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
656 AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
657 AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
658 AttributeValue::Aggregator { is_as4: false, .. } => AttrType::AGGREGATOR,
659 AttributeValue::Aggregator { is_as4: true, .. } => AttrType::AS4_AGGREGATOR,
660 AttributeValue::Communities(_) => AttrType::COMMUNITIES,
661 AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
662 AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
663 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
664 }
665 AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
666 AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
667 AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
668 AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
669 AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
670 AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
671 AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
672 AttributeValue::Development(_) => AttrType::DEVELOPMENT,
673 AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => x.attr_type,
674 AttributeValue::Aigp(_) => AttrType::AIGP,
675 AttributeValue::AttrSet(_) => AttrType::ATTR_SET,
676 }
677 }
678
679 pub fn attr_category(&self) -> Option<AttributeCategory> {
680 use AttributeCategory::*;
681
682 match self {
683 AttributeValue::Origin(_) => Some(WellKnownMandatory),
684 AttributeValue::AsPath { is_as4: false, .. } => Some(WellKnownMandatory),
685 AttributeValue::AsPath { is_as4: true, .. } => Some(OptionalTransitive),
686 AttributeValue::NextHop(_) => Some(WellKnownMandatory),
687 AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
688 AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
690 AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
691 AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
692 AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
693 AttributeValue::Communities(_) => Some(OptionalTransitive),
694 AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
695 AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
696 AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
697 AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
698 AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
699 AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
700 AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
701 AttributeValue::Aigp(_) => Some(OptionalNonTransitive),
702 AttributeValue::AttrSet(_) => Some(OptionalTransitive),
703 _ => None,
704 }
705 }
706
707 pub fn default_flags(&self) -> AttrFlags {
710 match self.attr_category() {
711 None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
712 Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
713 Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
714 Some(AttributeCategory::OptionalTransitive) => {
715 AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
716 }
717 Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
718 }
719 }
720}
721
722#[derive(Debug, PartialEq, Clone, Eq)]
723#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
724pub struct AttrRaw {
725 pub attr_type: AttrType,
726 pub bytes: Vec<u8>,
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732 use std::net::Ipv4Addr;
733 use std::str::FromStr;
734
735 #[test]
736 fn test_attr_type() {
737 let attr_value = AttributeValue::Origin(Origin::IGP);
738 assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
739 }
740
741 #[test]
742 fn test_attr_category() {
743 let attr_value = AttributeValue::Origin(Origin::IGP);
744 let category = attr_value.attr_category().unwrap();
745 assert_eq!(category, AttributeCategory::WellKnownMandatory);
746 }
747
748 #[test]
749 fn test_default_flags() {
750 let attr_value = AttributeValue::Origin(Origin::IGP);
751 let flags = attr_value.default_flags();
752 assert_eq!(flags, AttrFlags::TRANSITIVE);
753 }
754
755 #[test]
756 fn test_from_iter_attribute_value_uses_default_flags() {
757 let attributes = Attributes::from_iter(vec![
758 AttributeValue::Origin(Origin::IGP),
759 AttributeValue::AsPath {
760 path: AsPath::new(),
761 is_as4: false,
762 },
763 ]);
764
765 assert_eq!(
766 attributes.get_attr(AttrType::ORIGIN).unwrap().flag,
767 AttrFlags::TRANSITIVE
768 );
769 assert_eq!(
770 attributes.get_attr(AttrType::AS_PATH).unwrap().flag,
771 AttrFlags::TRANSITIVE
772 );
773 }
774
775 #[test]
776 fn test_get_attr() {
777 let attribute = Attribute {
778 value: AttributeValue::Origin(Origin::IGP),
779 flag: AttrFlags::TRANSITIVE,
780 };
781
782 let mut attributes = Attributes::default();
783 attributes.add_attr(attribute.clone());
784
785 assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
786 }
787
788 #[test]
789 fn test_has_attr() {
790 let attribute = Attribute {
791 value: AttributeValue::Origin(Origin::IGP),
792 flag: AttrFlags::TRANSITIVE,
793 };
794
795 let mut attributes = Attributes::default();
796 attributes.add_attr(attribute);
797
798 assert!(attributes.has_attr(AttrType::ORIGIN));
799 }
800
801 #[test]
802 fn test_getting_all_attributes() {
803 let mut attributes = Attributes::default();
804 attributes.add_attr(Attribute {
805 value: AttributeValue::Origin(Origin::IGP),
806 flag: AttrFlags::TRANSITIVE,
807 });
808 attributes.add_attr(Attribute {
809 value: AttributeValue::AsPath {
810 path: AsPath::new(),
811 is_as4: false,
812 },
813 flag: AttrFlags::TRANSITIVE,
814 });
815 attributes.add_attr(Attribute {
816 value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
817 flag: AttrFlags::TRANSITIVE,
818 });
819 attributes.add_attr(Attribute {
820 value: AttributeValue::MultiExitDiscriminator(1),
821 flag: AttrFlags::TRANSITIVE,
822 });
823
824 attributes.add_attr(Attribute {
825 value: AttributeValue::LocalPreference(1),
826 flag: AttrFlags::TRANSITIVE,
827 });
828 attributes.add_attr(Attribute {
829 value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
830 flag: AttrFlags::TRANSITIVE,
831 });
832 attributes.add_attr(Attribute {
833 value: AttributeValue::AtomicAggregate,
834 flag: AttrFlags::TRANSITIVE,
835 });
836 attributes.add_attr(Attribute {
837 value: AttributeValue::Clusters(vec![1, 2, 3]),
838 flag: AttrFlags::TRANSITIVE,
839 });
840 attributes.add_attr(Attribute {
841 value: AttributeValue::Aggregator {
842 asn: Asn::new_32bit(1),
843 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
844 is_as4: false,
845 },
846 flag: AttrFlags::TRANSITIVE,
847 });
848 attributes.add_attr(Attribute {
849 value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
850 flag: AttrFlags::TRANSITIVE,
851 });
852
853 assert_eq!(attributes.origin(), Origin::IGP);
854 assert_eq!(attributes.as_path(), Some(&AsPath::new()));
855 assert_eq!(
856 attributes.next_hop(),
857 Some(IpAddr::from_str("10.0.0.0").unwrap())
858 );
859 assert_eq!(attributes.multi_exit_discriminator(), Some(1));
860 assert_eq!(attributes.local_preference(), Some(1));
861 assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
862 assert!(attributes.atomic_aggregate());
863 assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
864 assert_eq!(
865 attributes.aggregator(),
866 Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
867 );
868 assert_eq!(
869 attributes.origin_id(),
870 Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
871 );
872
873 let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
874 assert!(aspath_attr.is_transitive());
875 assert!(!aspath_attr.is_extended());
876 assert!(!aspath_attr.is_partial());
877 assert!(!aspath_attr.is_optional());
878
879 for attr in attributes.iter() {
880 println!("{attr:?}");
881 }
882 }
883
884 #[test]
885 fn test_from() {
886 let origin = Origin::IGP;
887 let attr_value = AttributeValue::from(origin);
888 assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
889
890 let aspath = AsPath::new();
891 let attr_value = AttributeValue::from(aspath);
892 assert_eq!(
893 attr_value,
894 AttributeValue::AsPath {
895 path: AsPath::new(),
896 is_as4: false
897 }
898 );
899 }
900
901 #[test]
902 fn test_well_known_mandatory_attrs() {
903 let origin_attr = AttributeValue::Origin(Origin::IGP);
904 assert_eq!(
905 origin_attr.attr_category(),
906 Some(AttributeCategory::WellKnownMandatory)
907 );
908 let as_path_attr = AttributeValue::AsPath {
909 path: AsPath::new(),
910 is_as4: false,
911 };
912 assert_eq!(
913 as_path_attr.attr_category(),
914 Some(AttributeCategory::WellKnownMandatory)
915 );
916 let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
917 assert_eq!(
918 next_hop_attr.attr_category(),
919 Some(AttributeCategory::WellKnownMandatory)
920 );
921 let local_preference_attr = AttributeValue::LocalPreference(1);
922 assert_eq!(
923 local_preference_attr.attr_category(),
924 Some(AttributeCategory::WellKnownMandatory)
925 );
926 }
927
928 #[test]
929 fn test_well_known_discretionary_attrs() {
930 let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
931 assert_eq!(
932 atomic_aggregate_attr.attr_category(),
933 Some(AttributeCategory::WellKnownDiscretionary)
934 );
935 }
936
937 #[test]
938 fn test_optional_transitive_attrs() {
939 let as_path_attr = AttributeValue::AsPath {
940 path: AsPath::new(),
941 is_as4: true,
942 };
943 assert_eq!(
944 as_path_attr.attr_category(),
945 Some(AttributeCategory::OptionalTransitive)
946 );
947 let aggregator_attr = AttributeValue::Aggregator {
948 asn: Asn::new_32bit(1),
949 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
950 is_as4: false,
951 };
952 assert_eq!(
953 aggregator_attr.attr_category(),
954 Some(AttributeCategory::OptionalTransitive)
955 );
956 let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
957 assert_eq!(
958 only_to_customer_attr.attr_category(),
959 Some(AttributeCategory::OptionalTransitive)
960 );
961 let communities_attr =
962 AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
963 assert_eq!(
964 communities_attr.attr_category(),
965 Some(AttributeCategory::OptionalTransitive)
966 );
967 let extended_communities_attr =
968 AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
969 assert_eq!(
970 extended_communities_attr.attr_category(),
971 Some(AttributeCategory::OptionalTransitive)
972 );
973 let large_communities_attr =
974 AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
975 assert_eq!(
976 large_communities_attr.attr_category(),
977 Some(AttributeCategory::OptionalTransitive)
978 );
979 let aggregator_attr = AttributeValue::Aggregator {
980 asn: Asn::new_32bit(1),
981 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
982 is_as4: true,
983 };
984 assert_eq!(
985 aggregator_attr.attr_category(),
986 Some(AttributeCategory::OptionalTransitive)
987 );
988 }
989
990 #[test]
991 fn test_optional_non_transitive_attrs() {
992 let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
993 assert_eq!(
994 multi_exit_discriminator_attr.attr_category(),
995 Some(AttributeCategory::OptionalNonTransitive)
996 );
997 let originator_id_attr =
998 AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
999 assert_eq!(
1000 originator_id_attr.attr_category(),
1001 Some(AttributeCategory::OptionalNonTransitive)
1002 );
1003 let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
1004 assert_eq!(
1005 clusters_attr.attr_category(),
1006 Some(AttributeCategory::OptionalNonTransitive)
1007 );
1008 let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
1009 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1010 ));
1011 assert_eq!(
1012 mp_unreach_nlri_attr.attr_category(),
1013 Some(AttributeCategory::OptionalNonTransitive)
1014 );
1015
1016 let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1017 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1018 ));
1019 assert_eq!(
1020 mp_reach_nlri_attr.attr_category(),
1021 Some(AttributeCategory::OptionalNonTransitive)
1022 );
1023 }
1024
1025 #[test]
1026 #[cfg(feature = "serde")]
1027 fn test_serde() {
1028 let attributes = Attributes::from_iter(vec![
1029 Attribute {
1030 value: AttributeValue::Origin(Origin::IGP),
1031 flag: AttrFlags::TRANSITIVE,
1032 },
1033 Attribute {
1034 value: AttributeValue::AsPath {
1035 path: AsPath::new(),
1036 is_as4: false,
1037 },
1038 flag: AttrFlags::TRANSITIVE,
1039 },
1040 ]);
1041
1042 let serialized = serde_json::to_string(&attributes).unwrap();
1043 let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
1044
1045 assert_eq!(attributes, deserialized);
1046 }
1047}