1mod aspath;
3mod nlri;
4mod origin;
5
6use crate::models::network::*;
7use bitflags::bitflags;
8use bytes::Bytes;
9use num_enum::{FromPrimitive, IntoPrimitive};
10use std::cmp::Ordering;
11use std::iter::{FromIterator, Map};
12use std::net::IpAddr;
13use std::slice::Iter;
14use std::vec::IntoIter;
15
16use crate::error::BgpValidationWarning;
17use crate::models::*;
18
19pub use aspath::*;
20pub use nlri::*;
21pub use origin::*;
22
23bitflags! {
24 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48 pub struct AttrFlags: u8 {
49 const OPTIONAL = 0b10000000;
50 const TRANSITIVE = 0b01000000;
51 const PARTIAL = 0b00100000;
52 const EXTENDED = 0b00010000;
53 }
54}
55
56#[allow(non_camel_case_types)]
62#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, FromPrimitive, IntoPrimitive)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
65#[repr(u8)]
66pub enum AttrType {
67 RESERVED = 0,
68 ORIGIN = 1,
69 AS_PATH = 2,
70 NEXT_HOP = 3,
71 MULTI_EXIT_DISCRIMINATOR = 4,
72 LOCAL_PREFERENCE = 5,
73 ATOMIC_AGGREGATE = 6,
74 AGGREGATOR = 7,
75 COMMUNITIES = 8,
76 ORIGINATOR_ID = 9,
78 CLUSTER_LIST = 10,
79 MP_REACHABLE_NLRI = 14,
81 MP_UNREACHABLE_NLRI = 15,
82 EXTENDED_COMMUNITIES = 16,
84 AS4_PATH = 17,
85 AS4_AGGREGATOR = 18,
86 PMSI_TUNNEL = 22,
87 TUNNEL_ENCAPSULATION = 23,
88 TRAFFIC_ENGINEERING = 24,
89 IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES = 25,
90 AIGP = 26,
91 PE_DISTINGUISHER_LABELS = 27,
92 BGP_LS_ATTRIBUTE = 29,
93 LARGE_COMMUNITIES = 32,
94 BGPSEC_PATH = 33,
95 ONLY_TO_CUSTOMER = 35,
96 BGP_DOMAIN_PATH = 36,
97 SFP_ATTRIBUTE = 37,
98 BFD_DISCRIMINATOR = 38,
99 BGP_PREFIX_SID = 40,
100 BIER = 41,
101 ATTR_SET = 128,
102 DEVELOPMENT = 255,
104
105 #[num_enum(catch_all)]
107 Unknown(u8) = 254,
110}
111
112impl PartialOrd for AttrType {
113 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
114 Some(self.cmp(other))
115 }
116}
117
118impl Ord for AttrType {
119 fn cmp(&self, other: &Self) -> Ordering {
120 u8::from(*self).cmp(&u8::from(*other))
121 }
122}
123
124pub fn get_deprecated_attr_type(attr_type: u8) -> Option<&'static str> {
125 match attr_type {
126 11 => Some("DPA"),
127 12 => Some("ADVERTISER"),
128 13 => Some("RCID_PATH"),
129 19 => Some("SAFI Specific Attribute"),
130 20 => Some("Connector Attribute"),
131 21 => Some("AS_PATHLIMIT"),
132 28 => Some("BGP Entropy Label Capability"),
133 30 | 31 | 129 | 241 | 242 | 243 => Some("RFC8093"),
134
135 _ => None,
136 }
137}
138
139#[derive(PartialEq, Clone, Default, Eq)]
141pub struct Attributes {
142 pub(crate) inner: Vec<Attribute>,
145 pub(crate) validation_warnings: Vec<BgpValidationWarning>,
147 pub(crate) attr_mask: [u64; 4],
149}
150
151impl std::fmt::Debug for Attributes {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("Attributes")
154 .field("inner", &self.inner)
155 .field("validation_warnings", &self.validation_warnings)
156 .finish()
157 }
158}
159
160impl Attributes {
161 pub fn has_attr(&self, ty: AttrType) -> bool {
162 let attr = u8::from(ty);
163 (self.attr_mask[(attr / 64) as usize] & (1u64 << (attr % 64))) != 0
164 }
165
166 pub fn get_attr(&self, ty: AttrType) -> Option<Attribute> {
167 self.inner
168 .iter()
169 .find(|x| x.value.attr_type() == ty)
170 .cloned()
171 }
172
173 pub fn add_attr(&mut self, attr: Attribute) {
174 let ty = attr.value.attr_code();
175 self.attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
176 self.inner.push(attr);
177 }
178
179 pub fn check_mandatory_attributes(&mut self, is_announcement: bool, has_standard_nlri: bool) {
186 if !is_announcement {
187 return;
188 }
189
190 if !self.has_attr(AttrType::ORIGIN) {
192 self.validation_warnings
193 .push(BgpValidationWarning::MissingWellKnownAttribute {
194 attr_type: AttrType::ORIGIN,
195 });
196 }
197 if !self.has_attr(AttrType::AS_PATH) {
198 self.validation_warnings
199 .push(BgpValidationWarning::MissingWellKnownAttribute {
200 attr_type: AttrType::AS_PATH,
201 });
202 }
203
204 let has_mp_reach = self.has_attr(AttrType::MP_REACHABLE_NLRI);
208 if (has_standard_nlri || !has_mp_reach) && !self.has_attr(AttrType::NEXT_HOP) {
209 self.validation_warnings
210 .push(BgpValidationWarning::MissingWellKnownAttribute {
211 attr_type: AttrType::NEXT_HOP,
212 });
213 }
214 }
215
216 pub(crate) fn check_domain_path_with_classic_nlri(&mut self, has_classic_nlri: bool) {
220 if !has_classic_nlri {
221 return;
222 }
223
224 let has_domain_path = self
225 .inner
226 .iter()
227 .any(|attribute| matches!(attribute.value, AttributeValue::DomainPath(_)));
228 let already_reported = self.validation_warnings.iter().any(|warning| {
229 matches!(
230 warning,
231 BgpValidationWarning::OptionalAttributeError { attr_type, .. }
232 if *attr_type == AttrType::BGP_DOMAIN_PATH
233 )
234 });
235 if !has_domain_path || already_reported {
236 return;
237 }
238
239 self.add_validation_warning(BgpValidationWarning::OptionalAttributeError {
240 attr_type: AttrType::BGP_DOMAIN_PATH,
241 reason: "D-PATH is only valid on IPVPN or EVPN routes, this UPDATE also carries classic IPv4 unicast NLRI (AFI 1, SAFI 1): RFC 10039 §4 requires treat-as-withdraw".to_string(),
242 });
243 }
244
245 pub fn add_validation_warning(&mut self, warning: BgpValidationWarning) {
247 self.validation_warnings.push(warning);
248 }
249
250 pub fn validation_warnings(&self) -> &[BgpValidationWarning] {
252 &self.validation_warnings
253 }
254
255 pub fn has_validation_warnings(&self) -> bool {
257 !self.validation_warnings.is_empty()
258 }
259
260 pub fn origin(&self) -> Origin {
263 self.inner
264 .iter()
265 .find_map(|x| match &x.value {
266 AttributeValue::Origin(x) => Some(*x),
267 _ => None,
268 })
269 .unwrap_or(Origin::INCOMPLETE)
270 }
271
272 pub fn origin_id(&self) -> Option<BgpIdentifier> {
274 self.inner.iter().find_map(|x| match &x.value {
275 AttributeValue::OriginatorId(x) => Some(*x),
276 _ => None,
277 })
278 }
279
280 pub fn next_hop(&self) -> Option<IpAddr> {
285 self.inner.iter().find_map(|x| match &x.value {
286 AttributeValue::NextHop(x) => Some(*x),
287 _ => None,
288 })
289 }
290
291 pub fn multi_exit_discriminator(&self) -> Option<u32> {
292 self.inner.iter().find_map(|x| match &x.value {
293 AttributeValue::MultiExitDiscriminator(x) => Some(*x),
294 _ => None,
295 })
296 }
297
298 pub fn local_preference(&self) -> Option<u32> {
299 self.inner.iter().find_map(|x| match &x.value {
300 AttributeValue::LocalPreference(x) => Some(*x),
301 _ => None,
302 })
303 }
304
305 pub fn only_to_customer(&self) -> Option<Asn> {
306 self.inner.iter().find_map(|x| match &x.value {
307 AttributeValue::OnlyToCustomer(x) => Some(*x),
308 _ => None,
309 })
310 }
311
312 pub fn atomic_aggregate(&self) -> bool {
313 self.inner
314 .iter()
315 .any(|x| matches!(&x.value, AttributeValue::AtomicAggregate))
316 }
317
318 pub fn aggregator(&self) -> Option<(Asn, BgpIdentifier)> {
319 self.inner.iter().rev().find_map(|x| match &x.value {
322 AttributeValue::Aggregator { asn, id } | AttributeValue::As4Aggregator { asn, id } => {
323 Some((*asn, *id))
324 }
325 _ => None,
326 })
327 }
328
329 pub fn clusters(&self) -> Option<&[u32]> {
330 self.inner.iter().find_map(|x| match &x.value {
331 AttributeValue::Clusters(x) => Some(x.as_ref()),
332 _ => None,
333 })
334 }
335
336 pub fn as_path(&self) -> Option<&AsPath> {
342 self.inner.iter().find_map(|x| match &x.value {
343 AttributeValue::AsPath(path) => Some(path),
344 _ => None,
345 })
346 }
347
348 pub fn as4_path(&self) -> Option<&AsPath> {
350 self.inner.iter().find_map(|x| match &x.value {
351 AttributeValue::As4Path(path) => Some(path),
352 _ => None,
353 })
354 }
355
356 pub fn effective_as_path(&self) -> Option<AsPath> {
363 match (self.as_path(), self.as4_path()) {
364 (None, None) => None,
365 (Some(path), None) | (None, Some(path)) => Some(path.clone()),
366 (Some(path), Some(as4_path)) => Some(AsPath::merge_aspath_as4path(path, as4_path)),
367 }
368 }
369
370 pub fn get_reachable_nlri(&self) -> Option<&Nlri> {
371 self.inner.iter().find_map(|x| match &x.value {
372 AttributeValue::MpReachNlri(x) => Some(x),
373 _ => None,
374 })
375 }
376
377 pub fn get_unreachable_nlri(&self) -> Option<&Nlri> {
378 self.inner.iter().find_map(|x| match &x.value {
379 AttributeValue::MpUnreachNlri(x) => Some(x),
380 _ => None,
381 })
382 }
383
384 pub fn iter_communities(&self) -> MetaCommunitiesIter<'_> {
385 MetaCommunitiesIter {
386 attributes: &self.inner,
387 index: 0,
388 }
389 }
390
391 pub fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
394 self.into_iter()
395 }
396
397 pub fn into_attributes_iter(self) -> impl Iterator<Item = Attribute> {
400 self.inner.into_iter()
401 }
402}
403
404pub struct MetaCommunitiesIter<'a> {
405 attributes: &'a [Attribute],
406 index: usize,
407}
408
409impl Iterator for MetaCommunitiesIter<'_> {
410 type Item = MetaCommunity;
411
412 fn next(&mut self) -> Option<Self::Item> {
413 loop {
414 match &self.attributes.first()?.value {
415 AttributeValue::Communities(x) if self.index < x.len() => {
416 self.index += 1;
417 return Some(MetaCommunity::Plain(x[self.index - 1]));
418 }
419 AttributeValue::ExtendedCommunities(x) if self.index < x.len() => {
420 self.index += 1;
421 return Some(MetaCommunity::Extended(x[self.index - 1]));
422 }
423 AttributeValue::LargeCommunities(x) if self.index < x.len() => {
424 self.index += 1;
425 return Some(MetaCommunity::Large(x[self.index - 1]));
426 }
427 _ => {
428 self.attributes = &self.attributes[1..];
429 self.index = 0;
430 }
431 }
432 }
433 }
434}
435
436fn compute_mask(inner: &[Attribute]) -> [u64; 4] {
437 let mut attr_mask = [0; 4];
438 for attr in inner {
439 let ty = attr.value.attr_code();
440 attr_mask[(ty / 64) as usize] |= 1u64 << (ty % 64);
441 }
442 attr_mask
443}
444
445impl FromIterator<Attribute> for Attributes {
446 fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
447 let inner: Vec<Attribute> = iter.into_iter().collect();
448 let attr_mask = compute_mask(&inner);
449 Attributes {
450 inner,
451 validation_warnings: Vec::new(),
452 attr_mask,
453 }
454 }
455}
456
457impl From<Vec<Attribute>> for Attributes {
458 fn from(value: Vec<Attribute>) -> Self {
459 let attr_mask = compute_mask(&value);
460 Attributes {
461 inner: value,
462 validation_warnings: Vec::new(),
463 attr_mask,
464 }
465 }
466}
467
468impl Extend<Attribute> for Attributes {
469 fn extend<T: IntoIterator<Item = Attribute>>(&mut self, iter: T) {
470 for attr in iter {
471 self.add_attr(attr);
472 }
473 }
474}
475
476impl Extend<AttributeValue> for Attributes {
477 fn extend<T: IntoIterator<Item = AttributeValue>>(&mut self, iter: T) {
478 self.extend(iter.into_iter().map(Attribute::from))
479 }
480}
481
482impl FromIterator<AttributeValue> for Attributes {
483 fn from_iter<T: IntoIterator<Item = AttributeValue>>(iter: T) -> Self {
484 let inner: Vec<Attribute> = iter.into_iter().map(Attribute::from).collect();
485 let attr_mask = compute_mask(&inner);
486 Attributes {
487 inner,
488 validation_warnings: Vec::new(),
489 attr_mask,
490 }
491 }
492}
493
494impl IntoIterator for Attributes {
495 type Item = AttributeValue;
496 type IntoIter = Map<IntoIter<Attribute>, fn(Attribute) -> AttributeValue>;
497
498 fn into_iter(self) -> Self::IntoIter {
499 self.inner.into_iter().map(|x| x.value)
500 }
501}
502
503impl<'a> IntoIterator for &'a Attributes {
504 type Item = &'a AttributeValue;
505 type IntoIter = Map<Iter<'a, Attribute>, fn(&Attribute) -> &AttributeValue>;
506
507 fn into_iter(self) -> Self::IntoIter {
508 self.inner.iter().map(|x| &x.value)
509 }
510}
511
512#[cfg(feature = "serde")]
513mod serde_impl {
514 use super::*;
515 use serde::{Deserialize, Deserializer, Serialize, Serializer};
516
517 impl Serialize for Attributes {
518 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
519 where
520 S: Serializer,
521 {
522 self.inner.serialize(serializer)
523 }
524 }
525
526 impl<'de> Deserialize<'de> for Attributes {
527 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
528 where
529 D: Deserializer<'de>,
530 {
531 let inner = <Vec<Attribute>>::deserialize(deserializer)?;
532 let attr_mask = compute_mask(&inner);
533 Ok(Attributes {
534 inner,
535 validation_warnings: Vec::new(),
536 attr_mask,
537 })
538 }
539 }
540}
541
542#[derive(Debug, PartialEq, Clone, Eq)]
544#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
545#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
546pub struct Attribute {
547 pub value: AttributeValue,
548 #[cfg_attr(feature = "ts-rs", ts(type = "string"))]
550 pub flag: AttrFlags,
551}
552
553impl Attribute {
554 pub const fn is_optional(&self) -> bool {
555 self.flag.contains(AttrFlags::OPTIONAL)
556 }
557
558 pub const fn is_transitive(&self) -> bool {
559 self.flag.contains(AttrFlags::TRANSITIVE)
560 }
561
562 pub const fn is_partial(&self) -> bool {
563 self.flag.contains(AttrFlags::PARTIAL)
564 }
565
566 pub const fn is_extended(&self) -> bool {
567 self.flag.contains(AttrFlags::EXTENDED)
568 }
569}
570
571impl From<AttributeValue> for Attribute {
572 fn from(value: AttributeValue) -> Self {
573 Attribute {
574 flag: value.default_flags(),
575 value,
576 }
577 }
578}
579
580#[derive(Debug, PartialEq, Clone, Eq)]
582#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
583pub struct AigpTlv {
584 pub tlv_type: u8,
585 pub length: u16,
586 pub value: Bytes,
587}
588
589#[derive(Debug, PartialEq, Clone, Eq)]
594#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
595pub struct Aigp {
596 pub tlvs: Vec<AigpTlv>,
597}
598
599impl Aigp {
600 pub fn accumulated_metric(&self) -> Option<u64> {
602 self.tlvs
603 .iter()
604 .find(|tlv| tlv.tlv_type == 1)
605 .and_then(|tlv| {
606 if tlv.value.len() >= 8 {
607 Some(u64::from_be_bytes([
608 tlv.value[0],
609 tlv.value[1],
610 tlv.value[2],
611 tlv.value[3],
612 tlv.value[4],
613 tlv.value[5],
614 tlv.value[6],
615 tlv.value[7],
616 ]))
617 } else {
618 None
619 }
620 })
621 }
622}
623
624#[derive(Debug, PartialEq, Clone, Eq)]
626#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
627pub struct RawTlv8 {
628 pub tlv_type: u8,
629 pub value: Bytes,
630}
631
632#[derive(Debug, PartialEq, Clone, Eq)]
634#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
635pub struct RawTlv8Ext {
636 pub tlv_type: u8,
637 pub value: Bytes,
638}
639
640#[derive(Debug, PartialEq, Clone, Eq)]
642#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
643pub struct RawTlv16 {
644 pub tlv_type: u16,
645 pub value: Bytes,
646}
647
648#[derive(Debug, PartialEq, Clone, Eq)]
650#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
651pub struct BfdDiscriminatorAttribute {
652 pub mode: u8,
653 pub discriminator: u32,
654 pub tlvs: Vec<RawTlv8>,
655}
656
657#[derive(Debug, PartialEq, Clone, Eq)]
659#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
660pub struct BgpPrefixSidAttribute {
661 pub tlvs: Vec<RawTlv8Ext>,
662}
663
664#[derive(Debug, PartialEq, Clone, Eq)]
666#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
667pub struct BierAttribute {
668 pub tlvs: Vec<RawTlv16>,
669}
670
671#[derive(Debug, PartialEq, Clone, Eq)]
673#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
674pub struct SfpAttribute {
675 pub tlvs: Vec<RawTlv8Ext>,
676}
677
678#[derive(Debug, PartialEq, Clone, Eq)]
683#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
684pub struct DomainPathAttribute {
685 pub segments: Vec<DomainPathSegment>,
686}
687
688#[derive(Debug, PartialEq, Clone, Eq)]
692#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
693pub struct DomainPathSegment {
694 pub domains: Vec<DomainPathDomain>,
695}
696
697#[derive(Debug, PartialEq, Clone, Eq)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub struct DomainPathDomain {
704 pub global_admin: u32,
706 pub local_admin: u16,
708 pub isf_safi_type: u8,
710}
711
712#[derive(Debug, Clone)]
714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
715pub struct TrafficEngineering {
716 pub switching_capability: u8,
718
719 pub encoding: u8,
721
722 pub reserved: u16,
727
728 pub max_lsp_bandwidth: [f32; 8],
730
731 pub switching_capability_specific: Bytes,
733}
734
735impl PartialEq for TrafficEngineering {
736 fn eq(&self, other: &Self) -> bool {
737 self.switching_capability == other.switching_capability
738 && self.encoding == other.encoding
739 && self.reserved == other.reserved
740 && self
741 .max_lsp_bandwidth
742 .iter()
743 .zip(other.max_lsp_bandwidth.iter())
744 .all(|(left, right)| left.to_bits() == right.to_bits())
745 && self.switching_capability_specific == other.switching_capability_specific
746 }
747}
748
749impl Eq for TrafficEngineering {}
750
751#[derive(Debug, PartialEq, Clone, Eq)]
759#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
760pub struct AttrSet {
761 pub origin_as: Asn,
763 pub attributes: Attributes,
765}
766
767#[derive(Debug, PartialEq, Clone, Eq)]
779#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
780#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
781pub enum AttributeValue {
782 Origin(Origin),
783 #[cfg_attr(feature = "ts-rs", ts(as = "AsPathWire"))]
789 AsPath(AsPath),
790 #[cfg_attr(feature = "ts-rs", ts(as = "AsPathWire"))]
797 As4Path(AsPath),
798 NextHop(IpAddr),
799 MultiExitDiscriminator(u32),
800 LocalPreference(u32),
801 OnlyToCustomer(Asn),
802 AtomicAggregate,
803 Aggregator {
807 asn: Asn,
808 id: BgpIdentifier,
809 },
810 As4Aggregator {
814 asn: Asn,
815 id: BgpIdentifier,
816 },
817 Communities(Vec<Community>),
818 ExtendedCommunities(Vec<ExtendedCommunity>),
819 Ipv6AddressSpecificExtendedCommunities(Vec<Ipv6AddrExtCommunity>),
820 LargeCommunities(Vec<LargeCommunity>),
821 OriginatorId(BgpIdentifier),
822 Clusters(Vec<u32>),
823 MpReachNlri(Nlri),
824 MpUnreachNlri(Nlri),
825 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
827 LinkState(crate::models::bgp::linkstate::LinkStateAttribute),
828 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
830 TunnelEncapsulation(crate::models::bgp::tunnel_encap::TunnelEncapAttribute),
831 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
833 TrafficEngineering(TrafficEngineering),
834 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
836 BfdDiscriminator(BfdDiscriminatorAttribute),
837 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
839 BgpPrefixSid(BgpPrefixSidAttribute),
840 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
842 Bier(BierAttribute),
843 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
845 Sfp(SfpAttribute),
846 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
848 DomainPath(DomainPathAttribute),
849 Development(Vec<u8>),
850 Raw(AttrRaw),
851 Deprecated(AttrRaw),
852 Unknown(AttrRaw),
853 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
855 Aigp(Aigp),
856 #[cfg_attr(feature = "ts-rs", ts(type = "Record<string, unknown>"))]
858 AttrSet(AttrSet),
859}
860
861impl From<Origin> for AttributeValue {
862 fn from(value: Origin) -> Self {
863 AttributeValue::Origin(value)
864 }
865}
866
867impl From<AsPath> for AttributeValue {
870 fn from(path: AsPath) -> Self {
871 AttributeValue::AsPath(path)
872 }
873}
874
875#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
879#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
880pub enum AttributeCategory {
881 WellKnownMandatory,
882 WellKnownDiscretionary,
883 OptionalTransitive,
884 OptionalNonTransitive,
885}
886
887impl AttributeValue {
888 pub fn attr_type(&self) -> AttrType {
889 match self {
890 AttributeValue::Origin(_) => AttrType::ORIGIN,
891 AttributeValue::AsPath(_) => AttrType::AS_PATH,
892 AttributeValue::As4Path(_) => AttrType::AS4_PATH,
893 AttributeValue::NextHop(_) => AttrType::NEXT_HOP,
894 AttributeValue::MultiExitDiscriminator(_) => AttrType::MULTI_EXIT_DISCRIMINATOR,
895 AttributeValue::LocalPreference(_) => AttrType::LOCAL_PREFERENCE,
896 AttributeValue::OnlyToCustomer(_) => AttrType::ONLY_TO_CUSTOMER,
897 AttributeValue::AtomicAggregate => AttrType::ATOMIC_AGGREGATE,
898 AttributeValue::Aggregator { .. } => AttrType::AGGREGATOR,
899 AttributeValue::As4Aggregator { .. } => AttrType::AS4_AGGREGATOR,
900 AttributeValue::Communities(_) => AttrType::COMMUNITIES,
901 AttributeValue::ExtendedCommunities(_) => AttrType::EXTENDED_COMMUNITIES,
902 AttributeValue::Ipv6AddressSpecificExtendedCommunities(_) => {
903 AttrType::IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITIES
904 }
905 AttributeValue::LargeCommunities(_) => AttrType::LARGE_COMMUNITIES,
906 AttributeValue::OriginatorId(_) => AttrType::ORIGINATOR_ID,
907 AttributeValue::Clusters(_) => AttrType::CLUSTER_LIST,
908 AttributeValue::MpReachNlri(_) => AttrType::MP_REACHABLE_NLRI,
909 AttributeValue::MpUnreachNlri(_) => AttrType::MP_UNREACHABLE_NLRI,
910 AttributeValue::LinkState(_) => AttrType::BGP_LS_ATTRIBUTE,
911 AttributeValue::TunnelEncapsulation(_) => AttrType::TUNNEL_ENCAPSULATION,
912 AttributeValue::TrafficEngineering(_) => AttrType::TRAFFIC_ENGINEERING,
913 AttributeValue::BfdDiscriminator(_) => AttrType::BFD_DISCRIMINATOR,
914 AttributeValue::BgpPrefixSid(_) => AttrType::BGP_PREFIX_SID,
915 AttributeValue::Bier(_) => AttrType::BIER,
916 AttributeValue::Sfp(_) => AttrType::SFP_ATTRIBUTE,
917 AttributeValue::DomainPath(_) => AttrType::BGP_DOMAIN_PATH,
918 AttributeValue::Development(_) => AttrType::DEVELOPMENT,
919 AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
920 x.attr_type()
921 }
922 AttributeValue::Aigp(_) => AttrType::AIGP,
923 AttributeValue::AttrSet(_) => AttrType::ATTR_SET,
924 }
925 }
926
927 pub fn attr_code(&self) -> u8 {
928 match self {
929 AttributeValue::Raw(x) | AttributeValue::Deprecated(x) | AttributeValue::Unknown(x) => {
930 x.code
931 }
932 _ => self.attr_type().into(),
933 }
934 }
935
936 pub fn attr_category(&self) -> Option<AttributeCategory> {
937 use AttributeCategory::*;
938
939 match self {
940 AttributeValue::Origin(_) => Some(WellKnownMandatory),
941 AttributeValue::AsPath(_) => Some(WellKnownMandatory),
942 AttributeValue::As4Path(_) => Some(OptionalTransitive),
943 AttributeValue::NextHop(_) => Some(WellKnownMandatory),
944 AttributeValue::MultiExitDiscriminator(_) => Some(OptionalNonTransitive),
945 AttributeValue::LocalPreference(_) => Some(WellKnownMandatory),
947 AttributeValue::OnlyToCustomer(_) => Some(OptionalTransitive),
948 AttributeValue::AtomicAggregate => Some(WellKnownDiscretionary),
949 AttributeValue::Aggregator { .. } => Some(OptionalTransitive),
950 AttributeValue::As4Aggregator { .. } => Some(OptionalTransitive),
951 AttributeValue::Communities(_) => Some(OptionalTransitive),
952 AttributeValue::ExtendedCommunities(_) => Some(OptionalTransitive),
953 AttributeValue::LargeCommunities(_) => Some(OptionalTransitive),
954 AttributeValue::OriginatorId(_) => Some(OptionalNonTransitive),
955 AttributeValue::Clusters(_) => Some(OptionalNonTransitive),
956 AttributeValue::MpReachNlri(_) => Some(OptionalNonTransitive),
957 AttributeValue::MpUnreachNlri(_) => Some(OptionalNonTransitive),
958 AttributeValue::LinkState(_) => Some(OptionalNonTransitive),
959 AttributeValue::TrafficEngineering(_) => Some(OptionalNonTransitive),
960 AttributeValue::Aigp(_) => Some(OptionalNonTransitive),
961 AttributeValue::BfdDiscriminator(_) => Some(OptionalTransitive),
962 AttributeValue::BgpPrefixSid(_) => Some(OptionalTransitive),
963 AttributeValue::Bier(_) => Some(OptionalTransitive),
964 AttributeValue::Sfp(_) => Some(OptionalTransitive),
965 AttributeValue::DomainPath(_) => Some(OptionalTransitive),
966 AttributeValue::AttrSet(_) => Some(OptionalTransitive),
967 _ => None,
968 }
969 }
970
971 pub fn default_flags(&self) -> AttrFlags {
974 match self.attr_category() {
975 None => AttrFlags::OPTIONAL | AttrFlags::PARTIAL | AttrFlags::TRANSITIVE,
976 Some(AttributeCategory::WellKnownMandatory) => AttrFlags::TRANSITIVE,
977 Some(AttributeCategory::WellKnownDiscretionary) => AttrFlags::TRANSITIVE,
978 Some(AttributeCategory::OptionalTransitive) => {
979 AttrFlags::OPTIONAL | AttrFlags::TRANSITIVE
980 }
981 Some(AttributeCategory::OptionalNonTransitive) => AttrFlags::OPTIONAL,
982 }
983 }
984}
985
986#[derive(Debug, PartialEq, Clone, Eq)]
987#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
988#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
989pub struct AttrRaw {
990 pub code: u8,
991 #[cfg_attr(feature = "ts-rs", ts(type = "number[]"))]
993 pub bytes: Bytes,
994}
995
996impl AttrRaw {
997 pub fn attr_type(&self) -> AttrType {
1003 AttrType::from(self.code)
1004 }
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009 use super::*;
1010 use std::net::Ipv4Addr;
1011 use std::str::FromStr;
1012
1013 #[test]
1014 fn test_attr_type() {
1015 let attr_value = AttributeValue::Origin(Origin::IGP);
1016 assert_eq!(attr_value.attr_type(), AttrType::ORIGIN);
1017 }
1018
1019 #[test]
1020 fn test_attr_category() {
1021 let attr_value = AttributeValue::Origin(Origin::IGP);
1022 let category = attr_value.attr_category().unwrap();
1023 assert_eq!(category, AttributeCategory::WellKnownMandatory);
1024 }
1025
1026 #[test]
1027 fn test_default_flags() {
1028 let attr_value = AttributeValue::Origin(Origin::IGP);
1029 let flags = attr_value.default_flags();
1030 assert_eq!(flags, AttrFlags::TRANSITIVE);
1031 }
1032
1033 #[test]
1034 fn test_from_iter_attribute_value_uses_default_flags() {
1035 let attributes = Attributes::from_iter(vec![
1036 AttributeValue::Origin(Origin::IGP),
1037 AttributeValue::AsPath(AsPath::new()),
1038 ]);
1039
1040 assert_eq!(
1041 attributes.get_attr(AttrType::ORIGIN).unwrap().flag,
1042 AttrFlags::TRANSITIVE
1043 );
1044 assert_eq!(
1045 attributes.get_attr(AttrType::AS_PATH).unwrap().flag,
1046 AttrFlags::TRANSITIVE
1047 );
1048 }
1049
1050 #[test]
1051 fn test_get_attr() {
1052 let attribute = Attribute {
1053 value: AttributeValue::Origin(Origin::IGP),
1054 flag: AttrFlags::TRANSITIVE,
1055 };
1056
1057 let mut attributes = Attributes::default();
1058 attributes.add_attr(attribute.clone());
1059
1060 assert_eq!(attributes.get_attr(AttrType::ORIGIN), Some(attribute));
1061 }
1062
1063 #[test]
1064 fn test_has_attr() {
1065 let attribute = Attribute {
1066 value: AttributeValue::Origin(Origin::IGP),
1067 flag: AttrFlags::TRANSITIVE,
1068 };
1069
1070 let mut attributes = Attributes::default();
1071 attributes.add_attr(attribute);
1072
1073 assert!(attributes.has_attr(AttrType::ORIGIN));
1074 }
1075
1076 #[test]
1077 fn test_getting_all_attributes() {
1078 let mut attributes = Attributes::default();
1079 attributes.add_attr(Attribute {
1080 value: AttributeValue::Origin(Origin::IGP),
1081 flag: AttrFlags::TRANSITIVE,
1082 });
1083 attributes.add_attr(Attribute {
1084 value: AttributeValue::AsPath(AsPath::new()),
1085 flag: AttrFlags::TRANSITIVE,
1086 });
1087 attributes.add_attr(Attribute {
1088 value: AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap()),
1089 flag: AttrFlags::TRANSITIVE,
1090 });
1091 attributes.add_attr(Attribute {
1092 value: AttributeValue::MultiExitDiscriminator(1),
1093 flag: AttrFlags::TRANSITIVE,
1094 });
1095
1096 attributes.add_attr(Attribute {
1097 value: AttributeValue::LocalPreference(1),
1098 flag: AttrFlags::TRANSITIVE,
1099 });
1100 attributes.add_attr(Attribute {
1101 value: AttributeValue::OnlyToCustomer(Asn::new_32bit(1)),
1102 flag: AttrFlags::TRANSITIVE,
1103 });
1104 attributes.add_attr(Attribute {
1105 value: AttributeValue::AtomicAggregate,
1106 flag: AttrFlags::TRANSITIVE,
1107 });
1108 attributes.add_attr(Attribute {
1109 value: AttributeValue::Clusters(vec![1, 2, 3]),
1110 flag: AttrFlags::TRANSITIVE,
1111 });
1112 attributes.add_attr(Attribute {
1113 value: AttributeValue::Aggregator {
1114 asn: Asn::new_32bit(1),
1115 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1116 },
1117 flag: AttrFlags::TRANSITIVE,
1118 });
1119 attributes.add_attr(Attribute {
1120 value: AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap()),
1121 flag: AttrFlags::TRANSITIVE,
1122 });
1123
1124 assert_eq!(attributes.origin(), Origin::IGP);
1125 assert_eq!(attributes.as_path(), Some(&AsPath::new()));
1126 assert_eq!(
1127 attributes.next_hop(),
1128 Some(IpAddr::from_str("10.0.0.0").unwrap())
1129 );
1130 assert_eq!(attributes.multi_exit_discriminator(), Some(1));
1131 assert_eq!(attributes.local_preference(), Some(1));
1132 assert_eq!(attributes.only_to_customer(), Some(Asn::new_32bit(1)));
1133 assert!(attributes.atomic_aggregate());
1134 assert_eq!(attributes.clusters(), Some(vec![1_u32, 2, 3].as_slice()));
1135 assert_eq!(
1136 attributes.aggregator(),
1137 Some((Asn::new_32bit(1), Ipv4Addr::from_str("0.0.0.0").unwrap()))
1138 );
1139 assert_eq!(
1140 attributes.origin_id(),
1141 Some(Ipv4Addr::from_str("0.0.0.0").unwrap())
1142 );
1143
1144 let aspath_attr = attributes.get_attr(AttrType::AS_PATH).unwrap();
1145 assert!(aspath_attr.is_transitive());
1146 assert!(!aspath_attr.is_extended());
1147 assert!(!aspath_attr.is_partial());
1148 assert!(!aspath_attr.is_optional());
1149
1150 for attr in attributes.iter() {
1151 println!("{attr:?}");
1152 }
1153 }
1154
1155 #[test]
1156 fn test_from() {
1157 let origin = Origin::IGP;
1158 let attr_value = AttributeValue::from(origin);
1159 assert_eq!(attr_value, AttributeValue::Origin(Origin::IGP));
1160
1161 let aspath = AsPath::new();
1162 let attr_value = AttributeValue::from(aspath);
1163 assert_eq!(attr_value, AttributeValue::AsPath(AsPath::new()));
1164 }
1165
1166 #[test]
1167 fn test_well_known_mandatory_attrs() {
1168 let origin_attr = AttributeValue::Origin(Origin::IGP);
1169 assert_eq!(
1170 origin_attr.attr_category(),
1171 Some(AttributeCategory::WellKnownMandatory)
1172 );
1173 let as_path_attr = AttributeValue::AsPath(AsPath::new());
1174 assert_eq!(
1175 as_path_attr.attr_category(),
1176 Some(AttributeCategory::WellKnownMandatory)
1177 );
1178 let next_hop_attr = AttributeValue::NextHop(IpAddr::from_str("10.0.0.0").unwrap());
1179 assert_eq!(
1180 next_hop_attr.attr_category(),
1181 Some(AttributeCategory::WellKnownMandatory)
1182 );
1183 let local_preference_attr = AttributeValue::LocalPreference(1);
1184 assert_eq!(
1185 local_preference_attr.attr_category(),
1186 Some(AttributeCategory::WellKnownMandatory)
1187 );
1188 }
1189
1190 #[test]
1191 fn test_well_known_discretionary_attrs() {
1192 let atomic_aggregate_attr = AttributeValue::AtomicAggregate;
1193 assert_eq!(
1194 atomic_aggregate_attr.attr_category(),
1195 Some(AttributeCategory::WellKnownDiscretionary)
1196 );
1197 }
1198
1199 #[test]
1200 fn test_optional_transitive_attrs() {
1201 let as4_path_attr = AttributeValue::As4Path(AsPath::new());
1202 assert_eq!(
1203 as4_path_attr.attr_type(),
1204 AttrType::AS4_PATH,
1205 "AS4_PATH must map to wire type 17"
1206 );
1207 assert_eq!(
1208 as4_path_attr.attr_category(),
1209 Some(AttributeCategory::OptionalTransitive)
1210 );
1211 let aggregator_attr = AttributeValue::Aggregator {
1212 asn: Asn::new_32bit(1),
1213 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1214 };
1215 assert_eq!(
1216 aggregator_attr.attr_category(),
1217 Some(AttributeCategory::OptionalTransitive)
1218 );
1219 let only_to_customer_attr = AttributeValue::OnlyToCustomer(Asn::new_32bit(1));
1220 assert_eq!(
1221 only_to_customer_attr.attr_category(),
1222 Some(AttributeCategory::OptionalTransitive)
1223 );
1224 let communities_attr =
1225 AttributeValue::Communities(vec![Community::Custom(Asn::new_32bit(1), 1)]);
1226 assert_eq!(
1227 communities_attr.attr_category(),
1228 Some(AttributeCategory::OptionalTransitive)
1229 );
1230 let extended_communities_attr =
1231 AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([0; 8])]);
1232 assert_eq!(
1233 extended_communities_attr.attr_category(),
1234 Some(AttributeCategory::OptionalTransitive)
1235 );
1236 let large_communities_attr =
1237 AttributeValue::LargeCommunities(vec![LargeCommunity::new(1, [1, 1])]);
1238 assert_eq!(
1239 large_communities_attr.attr_category(),
1240 Some(AttributeCategory::OptionalTransitive)
1241 );
1242 let as4_aggregator_attr = AttributeValue::As4Aggregator {
1243 asn: Asn::new_32bit(1),
1244 id: Ipv4Addr::from_str("0.0.0.0").unwrap(),
1245 };
1246 assert_eq!(
1247 as4_aggregator_attr.attr_type(),
1248 AttrType::AS4_AGGREGATOR,
1249 "AS4_AGGREGATOR must map to wire type 18"
1250 );
1251 assert_eq!(
1252 as4_aggregator_attr.attr_category(),
1253 Some(AttributeCategory::OptionalTransitive)
1254 );
1255 }
1256
1257 #[test]
1258 fn test_new_attribute_attr_categories() {
1259 assert_eq!(
1261 AttributeValue::BfdDiscriminator(BfdDiscriminatorAttribute {
1262 mode: 1,
1263 discriminator: 0,
1264 tlvs: vec![],
1265 })
1266 .attr_category(),
1267 Some(AttributeCategory::OptionalTransitive)
1268 );
1269 assert_eq!(
1271 AttributeValue::BgpPrefixSid(BgpPrefixSidAttribute { tlvs: vec![] }).attr_category(),
1272 Some(AttributeCategory::OptionalTransitive)
1273 );
1274 assert_eq!(
1276 AttributeValue::Bier(BierAttribute { tlvs: vec![] }).attr_category(),
1277 Some(AttributeCategory::OptionalTransitive)
1278 );
1279 assert_eq!(
1281 AttributeValue::Sfp(SfpAttribute { tlvs: vec![] }).attr_category(),
1282 Some(AttributeCategory::OptionalTransitive)
1283 );
1284 assert_eq!(
1286 AttributeValue::DomainPath(DomainPathAttribute { segments: vec![] }).attr_category(),
1287 Some(AttributeCategory::OptionalTransitive)
1288 );
1289 }
1290
1291 #[test]
1292 fn test_optional_non_transitive_attrs() {
1293 let multi_exit_discriminator_attr = AttributeValue::MultiExitDiscriminator(1);
1294 assert_eq!(
1295 multi_exit_discriminator_attr.attr_category(),
1296 Some(AttributeCategory::OptionalNonTransitive)
1297 );
1298 let originator_id_attr =
1299 AttributeValue::OriginatorId(Ipv4Addr::from_str("0.0.0.0").unwrap());
1300 assert_eq!(
1301 originator_id_attr.attr_category(),
1302 Some(AttributeCategory::OptionalNonTransitive)
1303 );
1304 let clusters_attr = AttributeValue::Clusters(vec![1, 2, 3]);
1305 assert_eq!(
1306 clusters_attr.attr_category(),
1307 Some(AttributeCategory::OptionalNonTransitive)
1308 );
1309 let mp_unreach_nlri_attr = AttributeValue::MpReachNlri(Nlri::new_unreachable(
1310 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1311 ));
1312 assert_eq!(
1313 mp_unreach_nlri_attr.attr_category(),
1314 Some(AttributeCategory::OptionalNonTransitive)
1315 );
1316
1317 let mp_reach_nlri_attr = AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1318 NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
1319 ));
1320 assert_eq!(
1321 mp_reach_nlri_attr.attr_category(),
1322 Some(AttributeCategory::OptionalNonTransitive)
1323 );
1324
1325 let traffic_engineering_attr = AttributeValue::TrafficEngineering(TrafficEngineering {
1326 switching_capability: 1,
1327 encoding: 1,
1328 reserved: 0,
1329 max_lsp_bandwidth: [0.0; 8],
1330 switching_capability_specific: Bytes::new(),
1331 });
1332
1333 assert_eq!(
1334 traffic_engineering_attr.attr_category(),
1335 Some(AttributeCategory::OptionalNonTransitive)
1336 );
1337
1338 assert_eq!(
1339 traffic_engineering_attr.default_flags(),
1340 AttrFlags::OPTIONAL
1341 );
1342 }
1343
1344 #[test]
1345 #[cfg(feature = "serde")]
1346 fn test_serde() {
1347 let attributes = Attributes::from_iter(vec![
1348 Attribute {
1349 value: AttributeValue::Origin(Origin::IGP),
1350 flag: AttrFlags::TRANSITIVE,
1351 },
1352 Attribute {
1353 value: AttributeValue::AsPath(AsPath::new()),
1354 flag: AttrFlags::TRANSITIVE,
1355 },
1356 ]);
1357
1358 let serialized = serde_json::to_string(&attributes).unwrap();
1359 let deserialized: Attributes = serde_json::from_str(&serialized).unwrap();
1360
1361 assert_eq!(attributes, deserialized);
1362 }
1363
1364 #[test]
1365 fn test_as_path_accessors() {
1366 let mut attributes = Attributes::default();
1367 assert_eq!(attributes.as_path(), None);
1368 assert_eq!(attributes.as4_path(), None);
1369 assert_eq!(attributes.effective_as_path(), None);
1370
1371 attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([23456, 64497])).into());
1372 assert_eq!(
1373 attributes
1374 .as_path()
1375 .map(|p| p.to_u32_vec_opt(false).unwrap()),
1376 Some(vec![23456, 64497])
1377 );
1378 assert_eq!(attributes.as4_path(), None);
1379 assert_eq!(
1380 attributes
1381 .effective_as_path()
1382 .unwrap()
1383 .to_u32_vec_opt(false)
1384 .unwrap(),
1385 vec![23456, 64497]
1386 );
1387
1388 attributes.add_attr(AttributeValue::As4Path(AsPath::from_sequence([65536, 64497])).into());
1389 assert_eq!(
1390 attributes
1391 .as4_path()
1392 .map(|p| p.to_u32_vec_opt(false).unwrap()),
1393 Some(vec![65536, 64497])
1394 );
1395 assert_eq!(
1397 attributes
1398 .effective_as_path()
1399 .unwrap()
1400 .to_u32_vec_opt(false)
1401 .unwrap(),
1402 vec![65536, 64497]
1403 );
1404
1405 let mut attributes = Attributes::default();
1407 attributes.add_attr(AttributeValue::As4Path(AsPath::from_sequence([65536, 64497])).into());
1408 assert_eq!(attributes.as_path(), None);
1409 assert_eq!(
1410 attributes
1411 .effective_as_path()
1412 .unwrap()
1413 .to_u32_vec_opt(false)
1414 .unwrap(),
1415 vec![65536, 64497]
1416 );
1417 }
1418}