1use std::fmt;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4use bytes::Bytes;
5
6use crate::capability::{Afi, Safi};
7use crate::constants::{as_path_segment, attr_flags, attr_type};
8use crate::error::DecodeError;
9use crate::nlri::{NlriEntry, Prefix};
10use crate::notification::update_subcode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u8)]
15pub enum Origin {
16 Igp = 0,
18 Egp = 1,
20 Incomplete = 2,
22}
23
24impl Origin {
25 #[must_use]
27 pub fn from_u8(value: u8) -> Option<Self> {
28 match value {
29 0 => Some(Self::Igp),
30 1 => Some(Self::Egp),
31 2 => Some(Self::Incomplete),
32 _ => None,
33 }
34 }
35}
36
37impl std::fmt::Display for Origin {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Igp => write!(f, "IGP"),
41 Self::Egp => write!(f, "EGP"),
42 Self::Incomplete => write!(f, "INCOMPLETE"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub enum AsPathSegment {
50 AsSet(Vec<u32>),
52 AsSequence(Vec<u32>),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct AsPath {
59 pub segments: Vec<AsPathSegment>,
61}
62
63impl AsPath {
64 #[must_use]
67 pub fn len(&self) -> usize {
68 self.segments
69 .iter()
70 .map(|seg| match seg {
71 AsPathSegment::AsSequence(asns) => asns.len(),
72 AsPathSegment::AsSet(_) => 1,
73 })
74 .sum()
75 }
76
77 #[must_use]
79 pub fn is_empty(&self) -> bool {
80 self.segments.is_empty()
81 }
82
83 #[must_use]
86 pub fn contains_asn(&self, asn: u32) -> bool {
87 self.segments.iter().any(|seg| match seg {
88 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => asns.contains(&asn),
89 })
90 }
91
92 #[must_use]
98 pub fn origin_asn(&self) -> Option<u32> {
99 self.segments.iter().rev().find_map(|seg| match seg {
100 AsPathSegment::AsSequence(asns) => asns.last().copied(),
101 AsPathSegment::AsSet(_) => None,
102 })
103 }
104
105 #[must_use]
109 pub fn all_private(&self) -> bool {
110 let mut count = 0;
111 for seg in &self.segments {
112 match seg {
113 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => {
114 for asn in asns {
115 count += 1;
116 if !is_private_asn(*asn) {
117 return false;
118 }
119 }
120 }
121 }
122 }
123 count > 0
124 }
125
126 #[must_use]
134 pub fn to_aspath_string(&self) -> String {
135 let mut parts = Vec::new();
136 for seg in &self.segments {
137 match seg {
138 AsPathSegment::AsSequence(asns) => {
139 for asn in asns {
140 parts.push(asn.to_string());
141 }
142 }
143 AsPathSegment::AsSet(asns) => {
144 let inner: Vec<String> = asns.iter().map(ToString::to_string).collect();
145 parts.push(format!("{{{}}}", inner.join(" ")));
146 }
147 }
148 }
149 parts.join(" ")
150 }
151}
152
153#[must_use]
159pub fn is_private_asn(asn: u32) -> bool {
160 (64512..=65534).contains(&asn) || (4_200_000_000..=4_294_967_294).contains(&asn)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub struct MpReachNlri {
169 pub afi: Afi,
171 pub safi: Safi,
173 pub next_hop: IpAddr,
181 pub link_local_next_hop: Option<Ipv6Addr>,
186 pub announced: Vec<NlriEntry>,
188 pub flowspec_announced: Vec<crate::flowspec::FlowSpecRule>,
190 pub evpn_announced: Vec<crate::evpn::EvpnRoute>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub struct MpUnreachNlri {
200 pub afi: Afi,
202 pub safi: Safi,
204 pub withdrawn: Vec<NlriEntry>,
206 pub flowspec_withdrawn: Vec<crate::flowspec::FlowSpecRule>,
208 pub evpn_withdrawn: Vec<crate::evpn::EvpnRoute>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub struct ExtendedCommunity(u64);
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct DfElectionExtendedCommunity {
222 pub algorithm_id: u8,
224 pub capabilities: u16,
226 pub preference: Option<u16>,
229}
230
231impl ExtendedCommunity {
232 #[must_use]
234 pub fn new(raw: u64) -> Self {
235 Self(raw)
236 }
237
238 #[must_use]
240 pub fn as_u64(self) -> u64 {
241 self.0
242 }
243
244 #[must_use]
246 pub fn type_byte(self) -> u8 {
247 (self.0 >> 56) as u8
248 }
249
250 #[must_use]
252 pub fn subtype(self) -> u8 {
253 self.0.to_be_bytes()[1]
254 }
255
256 #[must_use]
258 pub fn is_transitive(self) -> bool {
259 self.type_byte() & 0x40 == 0
260 }
261
262 #[must_use]
264 pub fn value_bytes(self) -> [u8; 6] {
265 let b = self.0.to_be_bytes();
266 [b[2], b[3], b[4], b[5], b[6], b[7]]
267 }
268
269 #[must_use]
280 pub fn route_target(self) -> Option<(u32, u32)> {
281 if self.subtype() != 0x02 {
282 return None;
283 }
284 self.decode_two_part()
285 }
286
287 #[must_use]
294 pub fn route_origin(self) -> Option<(u32, u32)> {
295 if self.subtype() != 0x03 {
296 return None;
297 }
298 self.decode_two_part()
299 }
300
301 #[must_use]
319 pub fn as_bgp_encapsulation(self) -> Option<u16> {
320 if self.type_byte() & 0x3F != 0x03 || self.subtype() != 0x0C {
321 return None;
322 }
323 let v = self.value_bytes();
324 Some(u16::from_be_bytes([v[4], v[5]]))
325 }
326
327 #[must_use]
331 pub fn bgp_encapsulation(tunnel_type: u16) -> Self {
332 let tt = tunnel_type.to_be_bytes();
333 let raw = u64::from_be_bytes([0x03, 0x0C, 0, 0, 0, 0, tt[0], tt[1]]);
334 Self(raw)
335 }
336
337 #[must_use]
344 pub fn as_mac_mobility(self) -> Option<(bool, u32)> {
345 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x00 {
346 return None;
347 }
348 let v = self.value_bytes();
349 let sticky = (v[0] & 0x01) != 0;
350 let seq = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
351 Some((sticky, seq))
352 }
353
354 #[must_use]
356 pub fn mac_mobility(sticky: bool, sequence: u32) -> Self {
357 let flags = u8::from(sticky);
358 let s = sequence.to_be_bytes();
359 let raw = u64::from_be_bytes([0x06, 0x00, flags, 0, s[0], s[1], s[2], s[3]]);
360 Self(raw)
361 }
362
363 #[must_use]
369 pub fn as_esi_label(self) -> Option<(bool, u32)> {
370 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x01 {
371 return None;
372 }
373 let v = self.value_bytes();
374 let single_active = (v[0] & 0x01) != 0;
375 let label = (u32::from(v[3]) << 16) | (u32::from(v[4]) << 8) | u32::from(v[5]);
376 Some((single_active, label))
377 }
378
379 #[must_use]
383 pub fn esi_label(single_active: bool, label: u32) -> Self {
384 let flags = u8::from(single_active);
385 let l = label & 0x00FF_FFFF;
386 #[expect(clippy::cast_possible_truncation)]
387 let raw = u64::from_be_bytes([
388 0x06,
389 0x01,
390 flags,
391 0,
392 0,
393 (l >> 16) as u8,
394 (l >> 8) as u8,
395 l as u8,
396 ]);
397 Self(raw)
398 }
399
400 #[must_use]
406 pub fn as_es_import_rt(self) -> Option<[u8; 6]> {
407 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x02 {
408 return None;
409 }
410 Some(self.value_bytes())
411 }
412
413 #[must_use]
415 pub fn es_import_rt(mac: [u8; 6]) -> Self {
416 let raw = u64::from_be_bytes([0x06, 0x02, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
417 Self(raw)
418 }
419
420 #[must_use]
423 pub fn as_df_election(self) -> Option<DfElectionExtendedCommunity> {
424 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x06 {
425 return None;
426 }
427 let v = self.value_bytes();
428 let algorithm_id = v[0] & 0x1f;
429 let capabilities = u16::from_be_bytes([v[1], v[2]]);
430 let preference = match algorithm_id {
431 2 | 3 => Some(u16::from_be_bytes([v[4], v[5]])),
432 _ => None,
433 };
434 Some(DfElectionExtendedCommunity {
435 algorithm_id,
436 capabilities,
437 preference,
438 })
439 }
440
441 #[must_use]
448 pub fn df_election(algorithm_id: u8, capabilities: u16, preference: Option<u16>) -> Self {
449 let alg = algorithm_id & 0x1f;
450 let cap = capabilities.to_be_bytes();
451 let pref = preference.unwrap_or(0).to_be_bytes();
452 let raw = u64::from_be_bytes([0x06, 0x06, alg, cap[0], cap[1], 0, pref[0], pref[1]]);
453 Self(raw)
454 }
455
456 #[must_use]
462 pub fn as_link_bandwidth(self) -> Option<(u16, f32)> {
463 if self.type_byte() != 0x40 || self.subtype() != 0x04 {
464 return None;
465 }
466 let v = self.value_bytes();
467 let asn = u16::from_be_bytes([v[0], v[1]]);
468 let bytes_per_sec = f32::from_be_bytes([v[2], v[3], v[4], v[5]]);
469 Some((asn, bytes_per_sec))
470 }
471
472 #[must_use]
477 pub fn link_bandwidth(asn: u16, bytes_per_sec: f32) -> Self {
478 let a = asn.to_be_bytes();
479 let bw = bytes_per_sec.to_be_bytes();
480 let raw = u64::from_be_bytes([0x40, 0x04, a[0], a[1], bw[0], bw[1], bw[2], bw[3]]);
481 Self(raw)
482 }
483
484 #[must_use]
489 pub fn as_router_mac(self) -> Option<[u8; 6]> {
490 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x03 {
491 return None;
492 }
493 Some(self.value_bytes())
494 }
495
496 #[must_use]
498 pub fn router_mac(mac: [u8; 6]) -> Self {
499 let raw = u64::from_be_bytes([0x06, 0x03, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
500 Self(raw)
501 }
502
503 #[must_use]
510 pub fn as_default_gateway(self) -> bool {
511 self.type_byte() & 0x3F == 0x03 && self.subtype() == 0x0D && self.value_bytes() == [0u8; 6]
512 }
513
514 #[must_use]
516 pub fn default_gateway() -> Self {
517 let raw = u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0]);
518 Self(raw)
519 }
520
521 fn decode_two_part(self) -> Option<(u32, u32)> {
527 let v = self.value_bytes();
528 let t = self.type_byte() & 0x3F; match t {
530 0x00 => {
532 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
533 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
534 Some((global, local))
535 }
536 0x01 | 0x02 => {
538 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
539 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
540 Some((global, local))
541 }
542 _ => None,
543 }
544 }
545}
546
547impl fmt::Display for ExtendedCommunity {
548 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
550 if let Some((g, l)) = self.route_target() {
551 if is_ipv4 {
552 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
553 } else {
554 write!(f, "RT:{g}:{l}")
555 }
556 } else if let Some((g, l)) = self.route_origin() {
557 if is_ipv4 {
558 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
559 } else {
560 write!(f, "RO:{g}:{l}")
561 }
562 } else {
563 write!(f, "0x{:016x}", self.0)
564 }
565 }
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
572pub struct LargeCommunity {
573 pub global_admin: u32,
575 pub local_data1: u32,
577 pub local_data2: u32,
579}
580
581impl LargeCommunity {
582 #[must_use]
584 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
585 Self {
586 global_admin,
587 local_data1,
588 local_data2,
589 }
590 }
591}
592
593impl fmt::Display for LargeCommunity {
594 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
595 write!(
596 f,
597 "{}:{}:{}",
598 self.global_admin, self.local_data1, self.local_data2
599 )
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Hash)]
608pub enum PathAttribute {
609 Origin(Origin),
611 AsPath(AsPath),
613 NextHop(Ipv4Addr),
615 LocalPref(u32),
617 Med(u32),
619 Communities(Vec<u32>),
621 ExtendedCommunities(Vec<ExtendedCommunity>),
623 LargeCommunities(Vec<LargeCommunity>),
625 OriginatorId(Ipv4Addr),
627 ClusterList(Vec<Ipv4Addr>),
629 MpReachNlri(MpReachNlri),
631 MpUnreachNlri(MpUnreachNlri),
633 PmsiTunnel(crate::pmsi::PmsiTunnel),
636 Unknown(RawAttribute),
638}
639
640impl PathAttribute {
641 #[must_use]
643 pub fn type_code(&self) -> u8 {
644 match self {
645 Self::Origin(_) => attr_type::ORIGIN,
646 Self::AsPath(_) => attr_type::AS_PATH,
647 Self::NextHop(_) => attr_type::NEXT_HOP,
648 Self::LocalPref(_) => attr_type::LOCAL_PREF,
649 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
650 Self::Communities(_) => attr_type::COMMUNITIES,
651 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
652 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
653 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
654 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
655 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
656 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
657 Self::PmsiTunnel(_) => attr_type::PMSI_TUNNEL,
658 Self::Unknown(raw) => raw.type_code,
659 }
660 }
661
662 #[must_use]
664 pub fn flags(&self) -> u8 {
665 match self {
666 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
667 attr_flags::TRANSITIVE
668 }
669 Self::Med(_)
670 | Self::OriginatorId(_)
671 | Self::ClusterList(_)
672 | Self::MpReachNlri(_)
673 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
674 Self::Communities(_)
675 | Self::ExtendedCommunities(_)
676 | Self::LargeCommunities(_)
677 | Self::PmsiTunnel(_) => attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
678 Self::Unknown(raw) => raw.flags,
679 }
680 }
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Hash)]
688pub struct RawAttribute {
689 pub flags: u8,
691 pub type_code: u8,
693 pub data: Bytes,
695}
696
697pub fn decode_path_attributes(
708 mut buf: &[u8],
709 four_octet_as: bool,
710 add_path_families: &[(Afi, Safi)],
711) -> Result<Vec<PathAttribute>, DecodeError> {
712 let mut attrs = Vec::new();
713
714 while !buf.is_empty() {
715 if buf.len() < 2 {
717 return Err(DecodeError::MalformedField {
718 message_type: "UPDATE",
719 detail: "truncated attribute header".to_string(),
720 });
721 }
722
723 let flags = buf[0];
724 let type_code = buf[1];
725 buf = &buf[2..];
726
727 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
728 let value_len = if extended {
729 if buf.len() < 2 {
730 return Err(DecodeError::MalformedField {
731 message_type: "UPDATE",
732 detail: "truncated extended-length attribute".to_string(),
733 });
734 }
735 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
736 buf = &buf[2..];
737 len
738 } else {
739 if buf.is_empty() {
740 return Err(DecodeError::MalformedField {
741 message_type: "UPDATE",
742 detail: "truncated attribute length".to_string(),
743 });
744 }
745 let len = buf[0] as usize;
746 buf = &buf[1..];
747 len
748 };
749
750 if buf.len() < value_len {
751 return Err(DecodeError::MalformedField {
752 message_type: "UPDATE",
753 detail: format!(
754 "attribute type {type_code} value truncated: need {value_len}, have {}",
755 buf.len()
756 ),
757 });
758 }
759
760 let value = &buf[..value_len];
761 buf = &buf[value_len..];
762
763 let attr =
764 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
765 attrs.push(attr);
766 }
767
768 Ok(attrs)
769}
770
771#[expect(clippy::too_many_lines)]
773fn decode_attribute_value(
774 flags: u8,
775 type_code: u8,
776 value: &[u8],
777 four_octet_as: bool,
778 add_path_families: &[(Afi, Safi)],
779) -> Result<PathAttribute, DecodeError> {
780 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
782 if let Some(expected) = expected_flags(type_code)
783 && (flags & flags_mask) != expected
784 {
785 return Err(DecodeError::UpdateAttributeError {
786 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
787 data: attr_error_data(flags, type_code, value),
788 detail: format!(
789 "type {} flags {:#04x} (expected {:#04x})",
790 type_code,
791 flags & flags_mask,
792 expected
793 ),
794 });
795 }
796
797 match type_code {
798 attr_type::ORIGIN => {
799 if value.len() != 1 {
800 return Err(DecodeError::UpdateAttributeError {
801 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
802 data: attr_error_data(flags, type_code, value),
803 detail: format!("ORIGIN length {} (expected 1)", value.len()),
804 });
805 }
806 match Origin::from_u8(value[0]) {
807 Some(origin) => Ok(PathAttribute::Origin(origin)),
808 None => Err(DecodeError::UpdateAttributeError {
809 subcode: update_subcode::INVALID_ORIGIN,
810 data: attr_error_data(flags, type_code, value),
811 detail: format!("invalid ORIGIN value {}", value[0]),
812 }),
813 }
814 }
815
816 attr_type::AS_PATH => {
817 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
818 DecodeError::UpdateAttributeError {
819 subcode: update_subcode::MALFORMED_AS_PATH,
820 data: attr_error_data(flags, type_code, value),
821 detail: e.to_string(),
822 }
823 })?;
824 Ok(PathAttribute::AsPath(AsPath { segments }))
825 }
826
827 attr_type::NEXT_HOP => {
828 if value.len() != 4 {
829 return Err(DecodeError::UpdateAttributeError {
830 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
831 data: attr_error_data(flags, type_code, value),
832 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
833 });
834 }
835 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
836 Ok(PathAttribute::NextHop(addr))
837 }
838
839 attr_type::MULTI_EXIT_DISC => {
840 if value.len() != 4 {
841 return Err(DecodeError::UpdateAttributeError {
842 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
843 data: attr_error_data(flags, type_code, value),
844 detail: format!("MED length {} (expected 4)", value.len()),
845 });
846 }
847 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
848 Ok(PathAttribute::Med(med))
849 }
850
851 attr_type::LOCAL_PREF => {
852 if value.len() != 4 {
853 return Err(DecodeError::UpdateAttributeError {
854 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
855 data: attr_error_data(flags, type_code, value),
856 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
857 });
858 }
859 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
860 Ok(PathAttribute::LocalPref(lp))
861 }
862
863 attr_type::COMMUNITIES => {
864 if !value.len().is_multiple_of(4) {
865 return Err(DecodeError::UpdateAttributeError {
866 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
867 data: attr_error_data(flags, type_code, value),
868 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
869 });
870 }
871 let communities = value
872 .chunks_exact(4)
873 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
874 .collect();
875 Ok(PathAttribute::Communities(communities))
876 }
877
878 attr_type::EXTENDED_COMMUNITIES => {
879 if !value.len().is_multiple_of(8) {
880 return Err(DecodeError::UpdateAttributeError {
881 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
882 data: attr_error_data(flags, type_code, value),
883 detail: format!(
884 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
885 value.len()
886 ),
887 });
888 }
889 let communities = value
890 .chunks_exact(8)
891 .map(|c| {
892 ExtendedCommunity::new(u64::from_be_bytes([
893 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
894 ]))
895 })
896 .collect();
897 Ok(PathAttribute::ExtendedCommunities(communities))
898 }
899
900 attr_type::ORIGINATOR_ID => {
901 if value.len() != 4 {
902 return Err(DecodeError::UpdateAttributeError {
903 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
904 data: attr_error_data(flags, type_code, value),
905 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
906 });
907 }
908 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
909 Ok(PathAttribute::OriginatorId(addr))
910 }
911
912 attr_type::CLUSTER_LIST => {
913 if !value.len().is_multiple_of(4) {
914 return Err(DecodeError::UpdateAttributeError {
915 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
916 data: attr_error_data(flags, type_code, value),
917 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
918 });
919 }
920 let ids = value
921 .chunks_exact(4)
922 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
923 .collect();
924 Ok(PathAttribute::ClusterList(ids))
925 }
926
927 attr_type::LARGE_COMMUNITIES => {
928 if value.is_empty() || !value.len().is_multiple_of(12) {
929 return Err(DecodeError::UpdateAttributeError {
930 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
931 data: attr_error_data(flags, type_code, value),
932 detail: format!(
933 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
934 value.len()
935 ),
936 });
937 }
938 let communities = value
939 .chunks_exact(12)
940 .map(|c| {
941 LargeCommunity::new(
942 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
943 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
944 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
945 )
946 })
947 .collect();
948 Ok(PathAttribute::LargeCommunities(communities))
949 }
950
951 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
952 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
953
954 attr_type::PMSI_TUNNEL => {
955 let pmsi = crate::pmsi::PmsiTunnel::decode(value)?;
956 Ok(PathAttribute::PmsiTunnel(pmsi))
957 }
958
959 _ => Ok(PathAttribute::Unknown(RawAttribute {
961 flags,
962 type_code,
963 data: Bytes::copy_from_slice(value),
964 })),
965 }
966}
967
968#[expect(clippy::too_many_lines)]
973fn decode_mp_reach_nlri(
974 value: &[u8],
975 add_path_families: &[(Afi, Safi)],
976) -> Result<PathAttribute, DecodeError> {
977 if value.len() < 5 {
978 return Err(DecodeError::MalformedField {
979 message_type: "UPDATE",
980 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
981 });
982 }
983
984 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
985 let safi_raw = value[2];
986 let nh_len = value[3] as usize;
987
988 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
989 message_type: "UPDATE",
990 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
991 })?;
992 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
993 message_type: "UPDATE",
994 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
995 })?;
996
997 if value.len() < 4 + nh_len + 1 {
999 return Err(DecodeError::MalformedField {
1000 message_type: "UPDATE",
1001 detail: format!(
1002 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
1003 value.len()
1004 ),
1005 });
1006 }
1007
1008 let nh_bytes = &value[4..4 + nh_len];
1009 let mut link_local_next_hop: Option<Ipv6Addr> = None;
1011 let next_hop = if safi == Safi::FlowSpec {
1012 if nh_len != 0 {
1013 return Err(DecodeError::MalformedField {
1014 message_type: "UPDATE",
1015 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
1016 });
1017 }
1018 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
1019 } else {
1020 match afi {
1021 Afi::Ipv4 => match nh_len {
1022 4 => IpAddr::V4(Ipv4Addr::new(
1023 nh_bytes[0],
1024 nh_bytes[1],
1025 nh_bytes[2],
1026 nh_bytes[3],
1027 )),
1028 16 | 32 => {
1029 let mut octets = [0u8; 16];
1030 octets.copy_from_slice(&nh_bytes[..16]);
1031 if nh_len == 32 {
1032 let mut ll = [0u8; 16];
1033 ll.copy_from_slice(&nh_bytes[16..32]);
1034 link_local_next_hop = Some(Ipv6Addr::from(ll));
1035 }
1036 IpAddr::V6(Ipv6Addr::from(octets))
1037 }
1038 _ => {
1039 return Err(DecodeError::MalformedField {
1040 message_type: "UPDATE",
1041 detail: format!(
1042 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
1043 ),
1044 });
1045 }
1046 },
1047 Afi::Ipv6 => {
1048 if nh_len != 16 && nh_len != 32 {
1049 return Err(DecodeError::MalformedField {
1050 message_type: "UPDATE",
1051 detail: format!(
1052 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
1053 ),
1054 });
1055 }
1056 let mut octets = [0u8; 16];
1057 octets.copy_from_slice(&nh_bytes[..16]);
1058 if nh_len == 32 {
1059 let mut ll = [0u8; 16];
1060 ll.copy_from_slice(&nh_bytes[16..32]);
1061 link_local_next_hop = Some(Ipv6Addr::from(ll));
1062 }
1063 IpAddr::V6(Ipv6Addr::from(octets))
1064 }
1065 Afi::L2Vpn => match nh_len {
1066 4 => IpAddr::V4(Ipv4Addr::new(
1067 nh_bytes[0],
1068 nh_bytes[1],
1069 nh_bytes[2],
1070 nh_bytes[3],
1071 )),
1072 16 => {
1073 let mut octets = [0u8; 16];
1074 octets.copy_from_slice(&nh_bytes[..16]);
1075 IpAddr::V6(Ipv6Addr::from(octets))
1076 }
1077 _ => {
1078 return Err(DecodeError::MalformedField {
1079 message_type: "UPDATE",
1080 detail: format!(
1081 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
1082 ),
1083 });
1084 }
1085 },
1086 }
1087 };
1088
1089 let nlri_start = 4 + nh_len + 1;
1091 let nlri_bytes = &value[nlri_start..];
1092
1093 if safi == Safi::FlowSpec {
1095 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1096 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1097 afi,
1098 safi,
1099 next_hop,
1100 link_local_next_hop,
1101 announced: vec![],
1102 flowspec_announced: flowspec_rules,
1103 evpn_announced: vec![],
1104 }));
1105 }
1106
1107 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1109 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1110 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1111 afi,
1112 safi,
1113 next_hop,
1114 link_local_next_hop,
1115 announced: vec![],
1116 flowspec_announced: vec![],
1117 evpn_announced: routes,
1118 }));
1119 }
1120
1121 if safi == Safi::Evpn {
1125 return Err(DecodeError::MalformedField {
1126 message_type: "UPDATE",
1127 detail: format!(
1128 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1129 afi as u16
1130 ),
1131 });
1132 }
1133
1134 let add_path = add_path_families.contains(&(afi, safi));
1135 let announced = match (afi, add_path) {
1136 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1137 .into_iter()
1138 .map(|p| NlriEntry {
1139 path_id: 0,
1140 prefix: Prefix::V4(p),
1141 })
1142 .collect(),
1143 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1144 .into_iter()
1145 .map(|e| NlriEntry {
1146 path_id: e.path_id,
1147 prefix: Prefix::V4(e.prefix),
1148 })
1149 .collect(),
1150 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1151 .into_iter()
1152 .map(|p| NlriEntry {
1153 path_id: 0,
1154 prefix: Prefix::V6(p),
1155 })
1156 .collect(),
1157 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1158 (Afi::L2Vpn, _) => {
1159 return Err(DecodeError::MalformedField {
1160 message_type: "UPDATE",
1161 detail: format!(
1162 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1163 safi as u8
1164 ),
1165 });
1166 }
1167 };
1168
1169 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1170 afi,
1171 safi,
1172 next_hop,
1173 link_local_next_hop,
1174 announced,
1175 flowspec_announced: vec![],
1176 evpn_announced: vec![],
1177 }))
1178}
1179
1180fn decode_mp_unreach_nlri(
1185 value: &[u8],
1186 add_path_families: &[(Afi, Safi)],
1187) -> Result<PathAttribute, DecodeError> {
1188 if value.len() < 3 {
1189 return Err(DecodeError::MalformedField {
1190 message_type: "UPDATE",
1191 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1192 });
1193 }
1194
1195 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1196 let safi_raw = value[2];
1197
1198 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1199 message_type: "UPDATE",
1200 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1201 })?;
1202 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1203 message_type: "UPDATE",
1204 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1205 })?;
1206
1207 let withdrawn_bytes = &value[3..];
1208
1209 if safi == Safi::FlowSpec {
1211 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1212 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1213 afi,
1214 safi,
1215 withdrawn: vec![],
1216 flowspec_withdrawn: flowspec_rules,
1217 evpn_withdrawn: vec![],
1218 }));
1219 }
1220
1221 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1223 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1224 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1225 afi,
1226 safi,
1227 withdrawn: vec![],
1228 flowspec_withdrawn: vec![],
1229 evpn_withdrawn: routes,
1230 }));
1231 }
1232
1233 if safi == Safi::Evpn {
1237 return Err(DecodeError::MalformedField {
1238 message_type: "UPDATE",
1239 detail: format!(
1240 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1241 afi as u16
1242 ),
1243 });
1244 }
1245
1246 let add_path = add_path_families.contains(&(afi, safi));
1247 let withdrawn = match (afi, add_path) {
1248 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1249 .into_iter()
1250 .map(|p| NlriEntry {
1251 path_id: 0,
1252 prefix: Prefix::V4(p),
1253 })
1254 .collect(),
1255 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1256 .into_iter()
1257 .map(|e| NlriEntry {
1258 path_id: e.path_id,
1259 prefix: Prefix::V4(e.prefix),
1260 })
1261 .collect(),
1262 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1263 .into_iter()
1264 .map(|p| NlriEntry {
1265 path_id: 0,
1266 prefix: Prefix::V6(p),
1267 })
1268 .collect(),
1269 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1270 (Afi::L2Vpn, _) => {
1271 return Err(DecodeError::MalformedField {
1272 message_type: "UPDATE",
1273 detail: format!(
1274 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1275 safi as u8
1276 ),
1277 });
1278 }
1279 };
1280
1281 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1282 afi,
1283 safi,
1284 withdrawn,
1285 flowspec_withdrawn: vec![],
1286 evpn_withdrawn: vec![],
1287 }))
1288}
1289
1290fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1292 let as_size: usize = if four_octet_as { 4 } else { 2 };
1293 let mut segments = Vec::new();
1294
1295 while !buf.is_empty() {
1296 if buf.len() < 2 {
1297 return Err(DecodeError::MalformedField {
1298 message_type: "UPDATE",
1299 detail: "truncated AS_PATH segment header".to_string(),
1300 });
1301 }
1302
1303 let seg_type = buf[0];
1304 let seg_count = buf[1] as usize;
1305 buf = &buf[2..];
1306
1307 let needed = seg_count * as_size;
1308 if buf.len() < needed {
1309 return Err(DecodeError::MalformedField {
1310 message_type: "UPDATE",
1311 detail: format!(
1312 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1313 buf.len()
1314 ),
1315 });
1316 }
1317
1318 let mut asns = Vec::with_capacity(seg_count);
1319 for _ in 0..seg_count {
1320 let asn = if four_octet_as {
1321 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1322 buf = &buf[4..];
1323 v
1324 } else {
1325 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1326 buf = &buf[2..];
1327 v
1328 };
1329 asns.push(asn);
1330 }
1331
1332 match seg_type {
1333 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1334 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1335 _ => {
1336 return Err(DecodeError::MalformedField {
1337 message_type: "UPDATE",
1338 detail: format!("unknown AS_PATH segment type {seg_type}"),
1339 });
1340 }
1341 }
1342 }
1343
1344 Ok(segments)
1345}
1346
1347pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1350 let mut buf = Vec::with_capacity(3 + value.len());
1351 if value.len() > 255 {
1352 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1353 buf.push(type_code);
1354 #[expect(clippy::cast_possible_truncation)]
1355 let len = value.len() as u16;
1356 buf.extend_from_slice(&len.to_be_bytes());
1357 } else {
1358 buf.push(flags);
1359 buf.push(type_code);
1360 #[expect(clippy::cast_possible_truncation)]
1361 buf.push(value.len() as u8);
1362 }
1363 buf.extend_from_slice(value);
1364 buf
1365}
1366
1367fn expected_flags(type_code: u8) -> Option<u8> {
1370 match type_code {
1371 attr_type::ORIGIN
1373 | attr_type::AS_PATH
1374 | attr_type::NEXT_HOP
1375 | attr_type::LOCAL_PREF
1376 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1377 attr_type::MULTI_EXIT_DISC
1380 | attr_type::ORIGINATOR_ID
1381 | attr_type::CLUSTER_LIST
1382 | attr_type::MP_REACH_NLRI
1383 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1384 attr_type::AGGREGATOR
1386 | attr_type::COMMUNITIES
1387 | attr_type::EXTENDED_COMMUNITIES
1388 | attr_type::LARGE_COMMUNITIES
1389 | attr_type::PMSI_TUNNEL => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1390 _ => None,
1391 }
1392}
1393
1394#[expect(
1402 clippy::too_many_lines,
1403 reason = "dispatch arms are inherently O(variants); each new path attribute adds a small block"
1404)]
1405pub fn encode_path_attributes(
1406 attrs: &[PathAttribute],
1407 buf: &mut Vec<u8>,
1408 four_octet_as: bool,
1409 add_path_mp: bool,
1410) {
1411 for attr in attrs {
1412 let mut value = Vec::new();
1413 let flags;
1414 let type_code;
1415
1416 match attr {
1417 PathAttribute::Origin(origin) => {
1418 flags = attr_flags::TRANSITIVE;
1419 type_code = attr_type::ORIGIN;
1420 value.push(*origin as u8);
1421 }
1422 PathAttribute::AsPath(as_path) => {
1423 flags = attr_flags::TRANSITIVE;
1424 type_code = attr_type::AS_PATH;
1425 encode_as_path(as_path, &mut value, four_octet_as);
1426 }
1427 PathAttribute::NextHop(addr) => {
1428 flags = attr_flags::TRANSITIVE;
1429 type_code = attr_type::NEXT_HOP;
1430 value.extend_from_slice(&addr.octets());
1431 }
1432 PathAttribute::Med(med) => {
1433 flags = attr_flags::OPTIONAL;
1434 type_code = attr_type::MULTI_EXIT_DISC;
1435 value.extend_from_slice(&med.to_be_bytes());
1436 }
1437 PathAttribute::LocalPref(lp) => {
1438 flags = attr_flags::TRANSITIVE;
1439 type_code = attr_type::LOCAL_PREF;
1440 value.extend_from_slice(&lp.to_be_bytes());
1441 }
1442 PathAttribute::Communities(communities) => {
1443 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1444 type_code = attr_type::COMMUNITIES;
1445 for &c in communities {
1446 value.extend_from_slice(&c.to_be_bytes());
1447 }
1448 }
1449 PathAttribute::ExtendedCommunities(communities) => {
1450 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1451 type_code = attr_type::EXTENDED_COMMUNITIES;
1452 for &c in communities {
1453 value.extend_from_slice(&c.as_u64().to_be_bytes());
1454 }
1455 }
1456 PathAttribute::LargeCommunities(communities) => {
1457 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1458 type_code = attr_type::LARGE_COMMUNITIES;
1459 for &c in communities {
1460 value.extend_from_slice(&c.global_admin.to_be_bytes());
1461 value.extend_from_slice(&c.local_data1.to_be_bytes());
1462 value.extend_from_slice(&c.local_data2.to_be_bytes());
1463 }
1464 }
1465 PathAttribute::OriginatorId(addr) => {
1466 flags = attr_flags::OPTIONAL;
1467 type_code = attr_type::ORIGINATOR_ID;
1468 value.extend_from_slice(&addr.octets());
1469 }
1470 PathAttribute::ClusterList(ids) => {
1471 flags = attr_flags::OPTIONAL;
1472 type_code = attr_type::CLUSTER_LIST;
1473 for id in ids {
1474 value.extend_from_slice(&id.octets());
1475 }
1476 }
1477 PathAttribute::MpReachNlri(mp) => {
1478 flags = attr_flags::OPTIONAL;
1479 type_code = attr_type::MP_REACH_NLRI;
1480 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1481 }
1482 PathAttribute::MpUnreachNlri(mp) => {
1483 flags = attr_flags::OPTIONAL;
1484 type_code = attr_type::MP_UNREACH_NLRI;
1485 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1486 }
1487 PathAttribute::PmsiTunnel(pmsi) => {
1488 (flags, type_code) = (
1490 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
1491 attr_type::PMSI_TUNNEL,
1492 );
1493 pmsi.encode(&mut value);
1494 }
1495 PathAttribute::Unknown(raw) => {
1496 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1500 flags = if (raw.flags & optional_transitive) == optional_transitive {
1501 raw.flags | attr_flags::PARTIAL
1502 } else {
1503 raw.flags
1504 };
1505 type_code = raw.type_code;
1506 value.extend_from_slice(&raw.data);
1507 }
1508 }
1509
1510 if value.len() > 255 {
1512 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1513 buf.push(type_code);
1514 #[expect(clippy::cast_possible_truncation)]
1515 let len = value.len() as u16;
1516 buf.extend_from_slice(&len.to_be_bytes());
1517 } else {
1518 buf.push(flags);
1519 buf.push(type_code);
1520 #[expect(clippy::cast_possible_truncation)]
1521 buf.push(value.len() as u8);
1522 }
1523 buf.extend_from_slice(&value);
1524 }
1525}
1526
1527fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1532 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1533 buf.push(mp.safi as u8);
1534
1535 if mp.safi == Safi::FlowSpec {
1537 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1540 return;
1541 }
1542
1543 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1545 match mp.next_hop {
1546 IpAddr::V4(addr) => {
1547 buf.push(4);
1548 buf.extend_from_slice(&addr.octets());
1549 }
1550 IpAddr::V6(addr) => {
1551 buf.push(16);
1552 buf.extend_from_slice(&addr.octets());
1553 }
1554 }
1555 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1557 return;
1558 }
1559
1560 match (mp.next_hop, mp.link_local_next_hop) {
1561 (IpAddr::V4(addr), _) => {
1562 buf.push(4); buf.extend_from_slice(&addr.octets());
1564 }
1565 (IpAddr::V6(addr), Some(ll)) => {
1566 debug_assert!(
1578 (ll.segments()[0] & 0xffc0) == 0xfe80,
1579 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1580 );
1581 buf.push(32); buf.extend_from_slice(&addr.octets());
1583 buf.extend_from_slice(&ll.octets());
1584 }
1585 (IpAddr::V6(addr), None) => {
1586 buf.push(16); buf.extend_from_slice(&addr.octets());
1588 }
1589 }
1590
1591 buf.push(0); if add_path {
1594 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1595 } else {
1596 for entry in &mp.announced {
1597 match entry.prefix {
1598 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1599 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1600 }
1601 }
1602 }
1603}
1604
1605fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1609 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1610 buf.push(mp.safi as u8);
1611
1612 if mp.safi == Safi::FlowSpec {
1614 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1615 return;
1616 }
1617
1618 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1620 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1621 return;
1622 }
1623
1624 if add_path {
1625 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1626 } else {
1627 for entry in &mp.withdrawn {
1628 match entry.prefix {
1629 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1630 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1631 }
1632 }
1633 }
1634}
1635
1636fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1638 for segment in &as_path.segments {
1639 let (seg_type, asns) = match segment {
1640 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1641 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1642 };
1643 for chunk in asns.chunks(u8::MAX as usize) {
1644 buf.push(seg_type);
1645 #[expect(clippy::cast_possible_truncation)]
1646 buf.push(chunk.len() as u8);
1647 for &asn in chunk {
1648 if four_octet_as {
1649 buf.extend_from_slice(&asn.to_be_bytes());
1650 } else {
1651 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1654 buf.extend_from_slice(&as2.to_be_bytes());
1655 }
1656 }
1657 }
1658 }
1659}
1660
1661#[cfg(test)]
1662mod tests {
1663 use super::*;
1664
1665 #[test]
1666 fn mp_reach_evpn_attribute_roundtrip() {
1667 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1668
1669 let mp = MpReachNlri {
1670 afi: Afi::L2Vpn,
1671 safi: Safi::Evpn,
1672 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1673 link_local_next_hop: None,
1674 announced: vec![],
1675 flowspec_announced: vec![],
1676 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1677 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1678 ethernet_tag: EthernetTagId(100),
1679 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1680 })],
1681 };
1682 let attr = PathAttribute::MpReachNlri(mp);
1683
1684 let mut buf = Vec::new();
1685 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1686 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1687 assert_eq!(decoded.len(), 1);
1688 assert_eq!(attr, decoded[0]);
1689
1690 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1691 panic!("not MP_REACH after decode");
1692 };
1693 assert_eq!(dec.afi, Afi::L2Vpn);
1694 assert_eq!(dec.safi, Safi::Evpn);
1695 assert_eq!(dec.evpn_announced.len(), 1);
1696 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1697 }
1698
1699 #[test]
1710 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1711 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1712
1713 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1714 let mp = MpReachNlri {
1715 afi: Afi::L2Vpn,
1716 safi: Safi::Evpn,
1717 next_hop: IpAddr::V6(vtep_v6),
1718 link_local_next_hop: None,
1719 announced: vec![],
1720 flowspec_announced: vec![],
1721 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1722 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1723 ethernet_tag: EthernetTagId(100),
1724 originator_ip: IpAddr::V6(vtep_v6),
1725 })],
1726 };
1727 let attr = PathAttribute::MpReachNlri(mp.clone());
1728
1729 let mut buf = Vec::new();
1730 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1731
1732 let extended = (buf[0] & 0x10) != 0;
1743 let value_off = if extended { 4 } else { 3 };
1744 assert_eq!(
1745 buf[value_off + 3],
1746 16,
1747 "EVPN IPv6 NH-Len must be 16, not 32"
1748 );
1749 assert_eq!(
1750 &buf[value_off + 4..value_off + 20],
1751 &vtep_v6.octets(),
1752 "encoded VTEP next-hop bytes must match the input"
1753 );
1754
1755 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1756 assert_eq!(decoded.len(), 1);
1757 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1758
1759 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1760 panic!("not MP_REACH after decode");
1761 };
1762 assert_eq!(dec.afi, Afi::L2Vpn);
1763 assert_eq!(dec.safi, Safi::Evpn);
1764 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1765 assert!(
1766 dec.link_local_next_hop.is_none(),
1767 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1768 );
1769 assert_eq!(dec.evpn_announced.len(), 1);
1770 match &dec.evpn_announced[0] {
1771 EvpnRoute::Imet(imet) => {
1772 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1773 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1774 }
1775 other => panic!("expected IMET, got {other:?}"),
1776 }
1777 }
1778
1779 #[test]
1786 fn mp_reach_evpn_rejects_32byte_next_hop() {
1787 let mut attr = vec![0x80u8, 14, 37];
1793 attr.extend_from_slice(&[
1794 0x00, 0x19, 0x46, 0x20, ]);
1798 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1802 match err {
1803 DecodeError::MalformedField { detail, .. } => {
1804 assert!(
1805 detail.contains("L2VPN next-hop length 32"),
1806 "expected L2VPN NH-Len rejection, got: {detail}"
1807 );
1808 }
1809 other => panic!("expected MalformedField, got: {other:?}"),
1810 }
1811 }
1812
1813 #[test]
1814 fn mp_unreach_evpn_attribute_roundtrip() {
1815 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1816
1817 let mp = MpUnreachNlri {
1818 afi: Afi::L2Vpn,
1819 safi: Safi::Evpn,
1820 withdrawn: vec![],
1821 flowspec_withdrawn: vec![],
1822 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1823 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1824 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1825 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1826 })],
1827 };
1828 let attr = PathAttribute::MpUnreachNlri(mp);
1829 let mut buf = Vec::new();
1830 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1831 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1832 assert_eq!(decoded.len(), 1);
1833 assert_eq!(attr, decoded[0]);
1834 }
1835
1836 #[test]
1839 fn ext_comm_bgp_encapsulation_vxlan() {
1840 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1842 assert_eq!(c.subtype(), 0x0C);
1843 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1844 let b = c.as_u64().to_be_bytes();
1846 assert_eq!(b[2..6], [0, 0, 0, 0]);
1847 assert_eq!(&b[6..8], &[0, 8]);
1848 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1850 }
1851
1852 #[test]
1853 fn ext_comm_mac_mobility_sticky_and_sequence() {
1854 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1855 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1856 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1857 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1858 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1860 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1861 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1862 }
1863
1864 #[test]
1865 fn ext_comm_esi_label_flags_and_label() {
1866 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1867 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1868 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1869 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1870 }
1871
1872 #[test]
1873 fn ext_comm_es_import_rt_mac() {
1874 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1875 let e = ExtendedCommunity::es_import_rt(mac);
1876 assert_eq!(e.as_es_import_rt(), Some(mac));
1877 assert_eq!(e.type_byte(), 0x06);
1878 assert_eq!(e.subtype(), 0x02);
1879 }
1880
1881 #[test]
1882 fn ext_comm_df_election_hrw_roundtrips_reserved_bytes_zero() {
1883 let ec = ExtendedCommunity::df_election(1, 0, None);
1884 assert_eq!(ec.type_byte(), 0x06);
1885 assert_eq!(ec.subtype(), 0x06);
1886 assert_eq!(
1887 ec.as_df_election(),
1888 Some(DfElectionExtendedCommunity {
1889 algorithm_id: 1,
1890 capabilities: 0,
1891 preference: None,
1892 })
1893 );
1894 assert_eq!(ec.as_u64().to_be_bytes(), [0x06, 0x06, 0x01, 0, 0, 0, 0, 0]);
1895 }
1896
1897 #[test]
1898 fn ext_comm_df_election_preference_bytes_decode_for_rfc9785_algorithms() {
1899 let ec = ExtendedCommunity::df_election(3, 0x8000, Some(42));
1900 assert_eq!(
1901 ec.as_df_election(),
1902 Some(DfElectionExtendedCommunity {
1903 algorithm_id: 3,
1904 capabilities: 0x8000,
1905 preference: Some(42),
1906 })
1907 );
1908 }
1909
1910 #[test]
1911 fn ext_comm_router_mac() {
1912 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1913 let e = ExtendedCommunity::router_mac(mac);
1914 assert_eq!(e.as_router_mac(), Some(mac));
1915 }
1916
1917 #[test]
1918 fn ext_comm_link_bandwidth_roundtrips() {
1919 let bw = 1.25e9_f32; let e = ExtendedCommunity::link_bandwidth(65001, bw);
1921 assert_eq!(e.type_byte(), 0x40, "non-transitive two-octet-AS-specific");
1922 assert_eq!(e.subtype(), 0x04, "Link Bandwidth subtype");
1923 let (asn, decoded) = e.as_link_bandwidth().expect("decodes as link bandwidth");
1924 assert_eq!(asn, 65001);
1925 assert_eq!(decoded.to_bits(), bw.to_bits());
1927 }
1928
1929 #[test]
1930 fn ext_comm_link_bandwidth_decodes_known_wire_bytes() {
1931 let one = 1.0_f32.to_be_bytes();
1933 let raw = u64::from_be_bytes([0x40, 0x04, 0xFD, 0xE9, one[0], one[1], one[2], one[3]]);
1934 let (asn, bw) = ExtendedCommunity::new(raw)
1935 .as_link_bandwidth()
1936 .expect("decodes as link bandwidth");
1937 assert_eq!(asn, 65001);
1938 assert_eq!(bw.to_bits(), 1.0_f32.to_bits());
1939 }
1940
1941 #[test]
1942 fn ext_comm_link_bandwidth_rejects_wrong_type_or_subtype() {
1943 let transitive = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x04, 0, 0, 0, 0, 0, 0]));
1945 assert!(transitive.as_link_bandwidth().is_none());
1946 let wrong_sub = ExtendedCommunity::new(u64::from_be_bytes([0x40, 0x02, 0, 0, 0, 0, 0, 0]));
1948 assert!(wrong_sub.as_link_bandwidth().is_none());
1949 }
1950
1951 #[test]
1952 fn ext_comm_default_gateway_flag_only() {
1953 let d = ExtendedCommunity::default_gateway();
1954 assert!(d.as_default_gateway());
1955 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1957 }
1958
1959 #[test]
1963 fn ext_comm_default_gateway_rejects_nonzero_value() {
1964 let malformed =
1966 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1967 assert!(
1968 !malformed.as_default_gateway(),
1969 "default-gateway accessor must require all-zero value bytes"
1970 );
1971 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1973 }
1974
1975 #[test]
1976 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1977 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1979 assert_eq!(rt.as_mac_mobility(), None);
1980 assert_eq!(rt.as_esi_label(), None);
1981 assert_eq!(rt.as_es_import_rt(), None);
1982 assert_eq!(rt.as_router_mac(), None);
1983 assert!(rt.as_link_bandwidth().is_none());
1984 assert!(!rt.as_default_gateway());
1985 }
1986
1987 #[test]
1988 fn origin_from_u8_roundtrip() {
1989 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1990 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1991 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1992 assert_eq!(Origin::from_u8(3), None);
1993 }
1994
1995 #[test]
1996 fn origin_ordering() {
1997 assert!(Origin::Igp < Origin::Egp);
1998 assert!(Origin::Egp < Origin::Incomplete);
1999 }
2000
2001 #[test]
2002 fn as_path_length_calculation() {
2003 let path = AsPath {
2004 segments: vec![
2005 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
2006 AsPathSegment::AsSet(vec![65004, 65005]),
2007 ],
2008 };
2009 assert_eq!(path.len(), 4);
2011 }
2012
2013 #[test]
2014 fn as_path_empty() {
2015 let path = AsPath { segments: vec![] };
2016 assert!(path.is_empty());
2017 assert_eq!(path.len(), 0);
2018 }
2019
2020 #[test]
2021 fn contains_asn_in_sequence() {
2022 let path = AsPath {
2023 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2024 };
2025 assert!(path.contains_asn(65002));
2026 assert!(!path.contains_asn(65004));
2027 }
2028
2029 #[test]
2030 fn contains_asn_in_set() {
2031 let path = AsPath {
2032 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
2033 };
2034 assert!(path.contains_asn(65005));
2035 assert!(!path.contains_asn(65001));
2036 }
2037
2038 #[test]
2039 fn contains_asn_multiple_segments() {
2040 let path = AsPath {
2041 segments: vec![
2042 AsPathSegment::AsSequence(vec![65001, 65002]),
2043 AsPathSegment::AsSet(vec![65003]),
2044 ],
2045 };
2046 assert!(path.contains_asn(65001));
2047 assert!(path.contains_asn(65003));
2048 assert!(!path.contains_asn(65004));
2049 }
2050
2051 #[test]
2052 fn contains_asn_empty_path() {
2053 let path = AsPath { segments: vec![] };
2054 assert!(!path.contains_asn(65001));
2055 }
2056
2057 #[test]
2058 fn is_private_asn_boundaries() {
2059 assert!(!is_private_asn(64_511));
2061 assert!(is_private_asn(64_512));
2062 assert!(is_private_asn(65_534));
2063 assert!(!is_private_asn(65_535));
2064
2065 assert!(!is_private_asn(4_199_999_999));
2067 assert!(is_private_asn(4_200_000_000));
2068 assert!(is_private_asn(4_294_967_294));
2069 assert!(!is_private_asn(4_294_967_295));
2070 }
2071
2072 #[test]
2073 fn all_private_empty_path_is_false() {
2074 let path = AsPath { segments: vec![] };
2075 assert!(!path.all_private());
2076 }
2077
2078 #[test]
2079 fn all_private_mixed_segments() {
2080 let path = AsPath {
2081 segments: vec![
2082 AsPathSegment::AsSet(vec![64_512, 65_000]),
2083 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
2084 ],
2085 };
2086 assert!(path.all_private());
2087
2088 let non_private = AsPath {
2089 segments: vec![
2090 AsPathSegment::AsSet(vec![64_512, 65_000]),
2091 AsPathSegment::AsSequence(vec![65_535]),
2092 ],
2093 };
2094 assert!(!non_private.all_private());
2095 }
2096
2097 #[test]
2098 fn decode_origin_igp() {
2099 let buf = [0x40, 0x01, 0x01, 0x00];
2101 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2102 assert_eq!(attrs.len(), 1);
2103 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2104 }
2105
2106 #[test]
2107 fn decode_origin_egp() {
2108 let buf = [0x40, 0x01, 0x01, 0x01];
2109 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2110 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
2111 }
2112
2113 #[test]
2114 fn decode_origin_invalid_value() {
2115 let buf = [0x40, 0x01, 0x01, 0x05];
2117 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2118 match &err {
2119 DecodeError::UpdateAttributeError { subcode, .. } => {
2120 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2121 }
2122 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2123 }
2124 }
2125
2126 #[test]
2127 fn decode_next_hop() {
2128 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
2130 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2131 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2132 }
2133
2134 #[test]
2135 fn decode_med() {
2136 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
2138 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2139 assert_eq!(attrs[0], PathAttribute::Med(100));
2140 }
2141
2142 #[test]
2143 fn decode_local_pref() {
2144 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
2146 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2147 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
2148 }
2149
2150 #[test]
2151 fn decode_as_path_4byte() {
2152 let buf = [
2155 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2160 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2161 assert_eq!(
2162 attrs[0],
2163 PathAttribute::AsPath(AsPath {
2164 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2165 })
2166 );
2167 }
2168
2169 #[test]
2170 fn decode_as_path_2byte() {
2171 let buf = [
2174 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2179 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2180 assert_eq!(
2181 attrs[0],
2182 PathAttribute::AsPath(AsPath {
2183 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2184 })
2185 );
2186 }
2187
2188 #[test]
2189 fn decode_unknown_attribute_preserved() {
2190 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2192 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2193 assert_eq!(
2194 attrs[0],
2195 PathAttribute::Unknown(RawAttribute {
2196 flags: 0xC0,
2197 type_code: 99,
2198 data: Bytes::from_static(&[1, 2, 3]),
2199 })
2200 );
2201 }
2202
2203 #[test]
2204 fn decode_atomic_aggregate_as_unknown() {
2205 let buf = [0x40, 0x06, 0x00];
2207 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2208 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2209 }
2210
2211 #[test]
2212 fn decode_extended_length() {
2213 let buf = [
2216 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2221 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2222 assert_eq!(
2223 attrs[0],
2224 PathAttribute::AsPath(AsPath {
2225 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2226 })
2227 );
2228 }
2229
2230 #[test]
2231 fn decode_multiple_attributes() {
2232 let mut buf = Vec::new();
2233 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2235 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2237 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2239
2240 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2241 assert_eq!(attrs.len(), 3);
2242 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2243 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2244 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2245 }
2246
2247 #[test]
2248 fn roundtrip_attributes_4byte() {
2249 let attrs = vec![
2250 PathAttribute::Origin(Origin::Igp),
2251 PathAttribute::AsPath(AsPath {
2252 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2253 }),
2254 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2255 PathAttribute::Med(100),
2256 PathAttribute::LocalPref(200),
2257 ];
2258
2259 let mut buf = Vec::new();
2260 encode_path_attributes(&attrs, &mut buf, true, false);
2261 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2262 assert_eq!(decoded, attrs);
2263 }
2264
2265 #[test]
2266 fn roundtrip_attributes_2byte() {
2267 let attrs = vec![
2268 PathAttribute::Origin(Origin::Egp),
2269 PathAttribute::AsPath(AsPath {
2270 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2271 }),
2272 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2273 ];
2274
2275 let mut buf = Vec::new();
2276 encode_path_attributes(&attrs, &mut buf, false, false);
2277 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2278 assert_eq!(decoded, attrs);
2279 }
2280
2281 #[test]
2282 fn reject_truncated_attribute_header() {
2283 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2285 }
2286
2287 #[test]
2288 fn reject_truncated_attribute_value() {
2289 let buf = [0x40, 0x01, 0x01];
2291 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2292 }
2293
2294 #[test]
2295 fn reject_bad_origin_length() {
2296 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2298 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2299 }
2300
2301 #[test]
2302 fn as_path_with_set_and_sequence() {
2303 let attrs = vec![PathAttribute::AsPath(AsPath {
2305 segments: vec![
2306 AsPathSegment::AsSequence(vec![65001]),
2307 AsPathSegment::AsSet(vec![65002, 65003]),
2308 ],
2309 })];
2310
2311 let mut buf = Vec::new();
2312 encode_path_attributes(&attrs, &mut buf, true, false);
2313 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2314 assert_eq!(decoded, attrs);
2315 }
2316
2317 #[test]
2318 fn decode_communities_single() {
2319 let community: u32 = (65001 << 16) | 0x0064;
2322 let bytes = community.to_be_bytes();
2323 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2324 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2325 assert_eq!(attrs.len(), 1);
2326 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2327 }
2328
2329 #[test]
2330 fn decode_communities_multiple() {
2331 let c1: u32 = (65001 << 16) | 0x0064;
2332 let c2: u32 = (65002 << 16) | 0x00C8;
2333 let b1 = c1.to_be_bytes();
2334 let b2 = c2.to_be_bytes();
2335 let buf = [
2336 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2337 ];
2338 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2339 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2340 }
2341
2342 #[test]
2343 fn decode_communities_empty() {
2344 let buf = [0xC0, 0x08, 0x00];
2346 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2347 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2348 }
2349
2350 #[test]
2351 fn decode_communities_odd_length_rejected() {
2352 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2354 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2355 }
2356
2357 #[test]
2358 fn communities_roundtrip() {
2359 let c1: u32 = (65001 << 16) | 0x0064;
2360 let c2: u32 = (65002 << 16) | 0x00C8;
2361 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2362
2363 let mut buf = Vec::new();
2364 encode_path_attributes(&attrs, &mut buf, true, false);
2365 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2366 assert_eq!(decoded, attrs);
2367 }
2368
2369 #[test]
2370 fn communities_type_code_and_flags() {
2371 let attr = PathAttribute::Communities(vec![]);
2372 assert_eq!(attr.type_code(), 8);
2373 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2374 }
2375
2376 #[test]
2379 fn decode_extended_communities_single() {
2380 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2382 let bytes = ec.as_u64().to_be_bytes();
2383 let buf = [
2384 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2385 bytes[7],
2386 ];
2387 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2388 assert_eq!(attrs.len(), 1);
2389 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2390 }
2391
2392 #[test]
2393 fn decode_extended_communities_multiple() {
2394 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2395 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2396 let b1 = ec1.as_u64().to_be_bytes();
2397 let b2 = ec2.as_u64().to_be_bytes();
2398 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2400 buf.extend_from_slice(&b2);
2401 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2402 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2403 }
2404
2405 #[test]
2406 fn decode_extended_communities_empty() {
2407 let buf = [0xC0, 0x10, 0x00];
2408 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2409 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2410 }
2411
2412 #[test]
2413 fn decode_extended_communities_bad_length() {
2414 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2416 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2417 }
2418
2419 #[test]
2420 fn extended_communities_roundtrip() {
2421 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2422 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2423 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2424
2425 let mut buf = Vec::new();
2426 encode_path_attributes(&attrs, &mut buf, true, false);
2427 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2428 assert_eq!(decoded, attrs);
2429 }
2430
2431 #[test]
2432 fn extended_communities_type_code_and_flags() {
2433 let attr = PathAttribute::ExtendedCommunities(vec![]);
2434 assert_eq!(attr.type_code(), 16);
2435 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2436 }
2437
2438 #[test]
2439 fn extended_community_type_subtype() {
2440 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2442 assert_eq!(ec.type_byte(), 0x00);
2443 assert_eq!(ec.subtype(), 0x02);
2444 assert!(ec.is_transitive());
2445 }
2446
2447 #[test]
2448 fn extended_community_route_target() {
2449 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2451 assert_eq!(ec.route_target(), Some((65001, 100)));
2452 assert_eq!(ec.route_origin(), None);
2453
2454 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2456 assert_eq!(ec4.route_target(), Some((65537, 200)));
2457
2458 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2461 let (g, l) = ec_ipv4.route_target().unwrap();
2462 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2464 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2466 }
2467
2468 #[test]
2469 fn extended_community_is_transitive() {
2470 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2472 assert!(t.is_transitive());
2473
2474 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2476 assert!(!nt.is_transitive());
2477 }
2478
2479 #[test]
2480 fn extended_community_display() {
2481 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2482 assert_eq!(rt.to_string(), "RT:65001:100");
2483
2484 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2485 assert_eq!(ro.to_string(), "RO:65001:100");
2486
2487 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2489 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2490
2491 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2493 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2494
2495 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2497 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2498
2499 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2501 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2502 }
2503
2504 #[test]
2505 fn unknown_attribute_roundtrip() {
2506 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2509 flags: 0xC0,
2510 type_code: 99,
2511 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2512 })];
2513
2514 let mut buf = Vec::new();
2515 encode_path_attributes(&attrs, &mut buf, true, false);
2516 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2517 assert_eq!(
2518 decoded,
2519 vec![PathAttribute::Unknown(RawAttribute {
2520 flags: 0xE0, type_code: 99,
2522 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2523 })]
2524 );
2525 }
2526
2527 #[test]
2528 fn origin_with_optional_flag_rejected() {
2529 let buf = [0xC0, 0x01, 0x01, 0x00];
2531 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2532 match &err {
2533 DecodeError::UpdateAttributeError { subcode, .. } => {
2534 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2535 }
2536 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2537 }
2538 }
2539
2540 #[test]
2541 fn med_with_transitive_flag_rejected() {
2542 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2544 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2545 match &err {
2546 DecodeError::UpdateAttributeError { subcode, .. } => {
2547 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2548 }
2549 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2550 }
2551 }
2552
2553 #[test]
2554 fn communities_without_optional_rejected() {
2555 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2557 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2558 match &err {
2559 DecodeError::UpdateAttributeError { subcode, .. } => {
2560 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2561 }
2562 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2563 }
2564 }
2565
2566 #[test]
2567 fn next_hop_length_error_subcode() {
2568 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2570 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2571 match &err {
2572 DecodeError::UpdateAttributeError { subcode, .. } => {
2573 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2574 }
2575 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2576 }
2577 }
2578
2579 #[test]
2580 fn invalid_origin_value_subcode() {
2581 let buf = [0x40, 0x01, 0x01, 0x05];
2583 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2584 match &err {
2585 DecodeError::UpdateAttributeError { subcode, .. } => {
2586 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2587 }
2588 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2589 }
2590 }
2591
2592 #[test]
2593 fn as_path_bad_segment_subcode() {
2594 let buf = [
2596 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2600 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2601 match &err {
2602 DecodeError::UpdateAttributeError { subcode, .. } => {
2603 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2604 }
2605 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2606 }
2607 }
2608
2609 #[test]
2610 fn encode_unknown_transitive_sets_partial() {
2611 let attr = PathAttribute::Unknown(RawAttribute {
2612 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2614 data: Bytes::from_static(&[1, 2]),
2615 });
2616 let mut buf = Vec::new();
2617 encode_path_attributes(&[attr], &mut buf, true, false);
2618 assert_eq!(
2620 buf[0],
2621 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2622 );
2623 }
2624
2625 #[test]
2626 fn encode_unknown_wellknown_transitive_no_partial() {
2627 let attr = PathAttribute::Unknown(RawAttribute {
2629 flags: attr_flags::TRANSITIVE, type_code: 99,
2631 data: Bytes::from_static(&[1, 2]),
2632 });
2633 let mut buf = Vec::new();
2634 encode_path_attributes(&[attr], &mut buf, true, false);
2635 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2636 }
2637
2638 #[test]
2639 fn encode_unknown_nontransitive_no_partial() {
2640 let attr = PathAttribute::Unknown(RawAttribute {
2641 flags: attr_flags::OPTIONAL, type_code: 99,
2643 data: Bytes::from_static(&[1, 2]),
2644 });
2645 let mut buf = Vec::new();
2646 encode_path_attributes(&[attr], &mut buf, true, false);
2647 assert_eq!(buf[0], attr_flags::OPTIONAL);
2649 }
2650
2651 fn nlri(prefix: Prefix) -> NlriEntry {
2655 NlriEntry { path_id: 0, prefix }
2656 }
2657
2658 #[test]
2659 fn mp_reach_nlri_ipv6_roundtrip() {
2660 use crate::capability::{Afi, Safi};
2661 use crate::nlri::{Ipv6Prefix, Prefix};
2662
2663 let mp = MpReachNlri {
2664 afi: Afi::Ipv6,
2665 safi: Safi::Unicast,
2666 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2667 link_local_next_hop: None,
2668 announced: vec![
2669 nlri(Prefix::V6(Ipv6Prefix::new(
2670 "2001:db8:1::".parse().unwrap(),
2671 48,
2672 ))),
2673 nlri(Prefix::V6(Ipv6Prefix::new(
2674 "2001:db8:2::".parse().unwrap(),
2675 48,
2676 ))),
2677 ],
2678 flowspec_announced: vec![],
2679 evpn_announced: vec![],
2680 };
2681 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2682
2683 let mut buf = Vec::new();
2684 encode_path_attributes(&attrs, &mut buf, true, false);
2685 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2686 assert_eq!(decoded.len(), 1);
2687 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2688 }
2689
2690 #[test]
2691 fn mp_unreach_nlri_ipv6_roundtrip() {
2692 use crate::capability::{Afi, Safi};
2693 use crate::nlri::{Ipv6Prefix, Prefix};
2694
2695 let mp = MpUnreachNlri {
2696 afi: Afi::Ipv6,
2697 safi: Safi::Unicast,
2698 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2699 "2001:db8:1::".parse().unwrap(),
2700 48,
2701 )))],
2702 flowspec_withdrawn: vec![],
2703 evpn_withdrawn: vec![],
2704 };
2705 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2706
2707 let mut buf = Vec::new();
2708 encode_path_attributes(&attrs, &mut buf, true, false);
2709 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2710 assert_eq!(decoded.len(), 1);
2711 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2712 }
2713
2714 #[test]
2715 fn mp_reach_nlri_ipv4_roundtrip() {
2716 use crate::capability::{Afi, Safi};
2717 use crate::nlri::Prefix;
2718
2719 let mp = MpReachNlri {
2720 afi: Afi::Ipv4,
2721 safi: Safi::Unicast,
2722 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2723 link_local_next_hop: None,
2724 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2725 Ipv4Addr::new(10, 1, 0, 0),
2726 16,
2727 )))],
2728 flowspec_announced: vec![],
2729 evpn_announced: vec![],
2730 };
2731 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2732
2733 let mut buf = Vec::new();
2734 encode_path_attributes(&attrs, &mut buf, true, false);
2735 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2736 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2737 }
2738
2739 #[test]
2740 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2741 use crate::capability::{Afi, Safi};
2742 use crate::nlri::Prefix;
2743
2744 let mp = MpReachNlri {
2745 afi: Afi::Ipv4,
2746 safi: Safi::Unicast,
2747 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2748 link_local_next_hop: None,
2749 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2750 Ipv4Addr::new(10, 1, 0, 0),
2751 16,
2752 )))],
2753 flowspec_announced: vec![],
2754 evpn_announced: vec![],
2755 };
2756 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2757
2758 let mut buf = Vec::new();
2759 encode_path_attributes(&attrs, &mut buf, true, false);
2760 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2761 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2762 }
2763
2764 #[test]
2765 fn mp_reach_nlri_type_code_and_flags() {
2766 use crate::capability::{Afi, Safi};
2767
2768 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2769 afi: Afi::Ipv6,
2770 safi: Safi::Unicast,
2771 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2772 link_local_next_hop: None,
2773 announced: vec![],
2774 flowspec_announced: vec![],
2775 evpn_announced: vec![],
2776 });
2777 assert_eq!(attr.type_code(), 14);
2778 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2780 }
2781
2782 #[test]
2783 fn mp_unreach_nlri_type_code_and_flags() {
2784 use crate::capability::{Afi, Safi};
2785
2786 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2787 afi: Afi::Ipv6,
2788 safi: Safi::Unicast,
2789 withdrawn: vec![],
2790 flowspec_withdrawn: vec![],
2791 evpn_withdrawn: vec![],
2792 });
2793 assert_eq!(attr.type_code(), 15);
2794 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2795 }
2796
2797 #[test]
2798 fn mp_reach_nlri_empty_nlri() {
2799 use crate::capability::{Afi, Safi};
2800
2801 let mp = MpReachNlri {
2802 afi: Afi::Ipv6,
2803 safi: Safi::Unicast,
2804 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2805 link_local_next_hop: None,
2806 announced: vec![],
2807 flowspec_announced: vec![],
2808 evpn_announced: vec![],
2809 };
2810 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2811
2812 let mut buf = Vec::new();
2813 encode_path_attributes(&attrs, &mut buf, true, false);
2814 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2815 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2816 }
2817
2818 #[test]
2819 fn mp_reach_nlri_bad_flags_rejected() {
2820 let mut value = Vec::new();
2824 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.push(16); value.extend_from_slice(&"::1".parse::<Ipv6Addr>().unwrap().octets()); value.push(0); let mut buf = Vec::new();
2831 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2834 buf.push(value.len() as u8);
2835 buf.extend_from_slice(&value);
2836
2837 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2838 assert!(matches!(
2839 err,
2840 DecodeError::UpdateAttributeError {
2841 subcode: 4, ..
2843 }
2844 ));
2845 }
2846
2847 #[test]
2850 #[expect(clippy::cast_possible_truncation)]
2851 fn mp_reach_nlri_ipv4_addpath_decode() {
2852 use crate::capability::{Afi, Safi};
2853 use crate::nlri::Prefix;
2854
2855 let mut value = Vec::new();
2858 value.extend_from_slice(&1u16.to_be_bytes()); value.push(1); value.push(4); value.extend_from_slice(&[10, 0, 0, 1]); value.push(0); value.extend_from_slice(&42u32.to_be_bytes());
2865 value.push(16);
2866 value.extend_from_slice(&[10, 1]);
2867
2868 let mut buf = Vec::new();
2869 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2872 buf.extend_from_slice(&value);
2873
2874 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2876 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2877 panic!("expected MpReachNlri");
2878 };
2879 assert_eq!(mp.announced.len(), 1);
2880 assert_eq!(mp.announced[0].path_id, 42);
2881 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2882
2883 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2886 }
2887
2888 #[test]
2889 #[expect(clippy::cast_possible_truncation)]
2890 fn mp_reach_nlri_ipv6_addpath_decode() {
2891 use crate::capability::{Afi, Safi};
2892 use crate::nlri::{Ipv6Prefix, Prefix};
2893
2894 let mut value = Vec::new();
2896 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.push(16); value.extend_from_slice(&"2001:db8::1".parse::<Ipv6Addr>().unwrap().octets());
2900 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2903 value.push(48);
2904 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2905
2906 let mut buf = Vec::new();
2907 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2910 buf.extend_from_slice(&value);
2911
2912 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2913 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2914 panic!("expected MpReachNlri");
2915 };
2916 assert_eq!(mp.announced.len(), 1);
2917 assert_eq!(mp.announced[0].path_id, 99);
2918 assert_eq!(
2919 mp.announced[0].prefix,
2920 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2921 );
2922 }
2923
2924 #[test]
2925 #[expect(clippy::cast_possible_truncation)]
2926 fn mp_unreach_nlri_ipv6_addpath_decode() {
2927 use crate::capability::{Afi, Safi};
2928 use crate::nlri::{Ipv6Prefix, Prefix};
2929
2930 let mut value = Vec::new();
2932 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2936 value.push(48);
2937 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2938
2939 let mut buf = Vec::new();
2940 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2943 buf.extend_from_slice(&value);
2944
2945 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2946 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2947 panic!("expected MpUnreachNlri");
2948 };
2949 assert_eq!(mp.withdrawn.len(), 1);
2950 assert_eq!(mp.withdrawn[0].path_id, 7);
2951 assert_eq!(
2952 mp.withdrawn[0].prefix,
2953 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2954 );
2955 }
2956
2957 #[test]
2958 fn mp_reach_addpath_only_applies_to_matching_family() {
2959 use crate::capability::{Afi, Safi};
2960 use crate::nlri::{Ipv6Prefix, Prefix};
2961
2962 let mp = MpReachNlri {
2964 afi: Afi::Ipv6,
2965 safi: Safi::Unicast,
2966 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2967 link_local_next_hop: None,
2968 announced: vec![NlriEntry {
2969 path_id: 0,
2970 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2971 }],
2972 flowspec_announced: vec![],
2973 evpn_announced: vec![],
2974 };
2975 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2976
2977 let mut buf = Vec::new();
2978 encode_path_attributes(&attrs, &mut buf, true, false);
2979
2980 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2982 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2983 }
2984
2985 #[test]
2988 fn decode_originator_id() {
2989 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2991 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2992 assert_eq!(
2993 attrs[0],
2994 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2995 );
2996 }
2997
2998 #[test]
3003 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
3004 use crate::capability::{Afi, Safi};
3005 use crate::nlri::{Ipv6Prefix, Prefix};
3006 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
3007 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
3008 let mp = MpReachNlri {
3009 afi: Afi::Ipv6,
3010 safi: Safi::Unicast,
3011 next_hop: IpAddr::V6(global),
3012 link_local_next_hop: Some(link_local),
3013 announced: vec![NlriEntry {
3014 path_id: 0,
3015 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
3016 }],
3017 flowspec_announced: vec![],
3018 evpn_announced: vec![],
3019 };
3020 let attr = PathAttribute::MpReachNlri(mp.clone());
3021 let mut buf = Vec::new();
3022 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3023
3024 let extended = (buf[0] & 0x10) != 0;
3028 let value_off = if extended { 4 } else { 3 };
3029 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
3031 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
3032 assert_eq!(
3033 &buf[value_off + 20..value_off + 36],
3034 &link_local.octets(),
3035 "encoded link-local bytes must match the input"
3036 );
3037
3038 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3039 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
3040 panic!("expected MpReachNlri");
3041 };
3042 assert_eq!(dec.next_hop, IpAddr::V6(global));
3043 assert_eq!(dec.link_local_next_hop, Some(link_local));
3044 }
3045
3046 #[test]
3054 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
3055 let value: &[u8] = &[
3058 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
3065 ];
3066 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
3069 attr.extend_from_slice(value);
3070 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
3071 match err {
3072 DecodeError::MalformedField { detail, .. } => {
3073 assert!(
3074 detail.contains("FlowSpec next-hop length"),
3075 "expected FlowSpec NH-Len rejection, got: {detail}"
3076 );
3077 }
3078 other => panic!("expected MalformedField, got {other:?}"),
3079 }
3080 }
3081
3082 #[test]
3083 fn originator_id_roundtrip() {
3084 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
3085 let mut buf = Vec::new();
3086 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3087 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3088 assert_eq!(decoded, vec![attr]);
3089 }
3090
3091 #[test]
3092 fn originator_id_wrong_length() {
3093 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
3095 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3096 assert!(matches!(
3097 err,
3098 DecodeError::UpdateAttributeError {
3099 subcode: 5, ..
3101 }
3102 ));
3103 }
3104
3105 #[test]
3106 fn originator_id_wrong_flags() {
3107 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
3109 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3110 assert!(matches!(
3111 err,
3112 DecodeError::UpdateAttributeError {
3113 subcode: 4, ..
3115 }
3116 ));
3117 }
3118
3119 #[test]
3122 fn decode_cluster_list() {
3123 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
3125 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3126 assert_eq!(
3127 attrs[0],
3128 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
3129 );
3130 }
3131
3132 #[test]
3133 fn cluster_list_roundtrip() {
3134 let attr = PathAttribute::ClusterList(vec![
3135 Ipv4Addr::new(10, 0, 0, 1),
3136 Ipv4Addr::new(10, 0, 0, 2),
3137 ]);
3138 let mut buf = Vec::new();
3139 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3140 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3141 assert_eq!(decoded, vec![attr]);
3142 }
3143
3144 #[test]
3145 fn cluster_list_wrong_length() {
3146 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
3148 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3149 assert!(matches!(
3150 err,
3151 DecodeError::UpdateAttributeError {
3152 subcode: 5, ..
3154 }
3155 ));
3156 }
3157
3158 #[test]
3163 fn large_community_display() {
3164 let lc = LargeCommunity::new(65001, 100, 200);
3165 assert_eq!(lc.to_string(), "65001:100:200");
3166 }
3167
3168 #[test]
3169 fn large_community_type_code_and_flags() {
3170 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3171 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3172 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3173 }
3174
3175 #[test]
3176 fn decode_large_community_single() {
3177 let mut buf = vec![0xC0, 32, 12];
3179 buf.extend_from_slice(&65001u32.to_be_bytes());
3180 buf.extend_from_slice(&100u32.to_be_bytes());
3181 buf.extend_from_slice(&200u32.to_be_bytes());
3182 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3183 assert_eq!(attrs.len(), 1);
3184 assert_eq!(
3185 attrs[0],
3186 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3187 );
3188 }
3189
3190 #[test]
3191 fn decode_large_community_multiple() {
3192 let mut buf = vec![0xC0, 32, 24];
3194 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3195 buf.extend_from_slice(&g.to_be_bytes());
3196 buf.extend_from_slice(&l1.to_be_bytes());
3197 buf.extend_from_slice(&l2.to_be_bytes());
3198 }
3199 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3200 assert_eq!(
3201 attrs[0],
3202 PathAttribute::LargeCommunities(vec![
3203 LargeCommunity::new(65001, 100, 200),
3204 LargeCommunity::new(65002, 300, 400),
3205 ])
3206 );
3207 }
3208
3209 #[test]
3210 fn decode_large_community_bad_length() {
3211 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3213 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3214 assert!(matches!(
3215 err,
3216 DecodeError::UpdateAttributeError {
3217 subcode: 5, ..
3219 }
3220 ));
3221 }
3222
3223 #[test]
3224 fn decode_large_community_empty_rejected() {
3225 let buf = [0xC0, 32, 0];
3227 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3228 assert!(matches!(
3229 err,
3230 DecodeError::UpdateAttributeError {
3231 subcode: 5, ..
3233 }
3234 ));
3235 }
3236
3237 #[test]
3238 fn large_community_roundtrip() {
3239 let lcs = vec![
3240 LargeCommunity::new(65001, 100, 200),
3241 LargeCommunity::new(0, u32::MAX, 42),
3242 ];
3243 let attr = PathAttribute::LargeCommunities(lcs.clone());
3244 let mut buf = Vec::new();
3245 encode_path_attributes(&[attr], &mut buf, true, false);
3246 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3247 assert_eq!(decoded.len(), 1);
3248 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3249 }
3250
3251 #[test]
3252 fn large_community_expected_flags_validated() {
3253 let mut buf = vec![0x40, 32, 12];
3255 buf.extend_from_slice(&1u32.to_be_bytes());
3256 buf.extend_from_slice(&2u32.to_be_bytes());
3257 buf.extend_from_slice(&3u32.to_be_bytes());
3258 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3259 assert!(matches!(
3260 err,
3261 DecodeError::UpdateAttributeError {
3262 subcode: 4, ..
3264 }
3265 ));
3266 }
3267
3268 #[test]
3273 fn aspath_string_sequence() {
3274 let p = AsPath {
3275 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3276 };
3277 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3278 }
3279
3280 #[test]
3281 fn aspath_string_set() {
3282 let p = AsPath {
3283 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3284 };
3285 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3286 }
3287
3288 #[test]
3289 fn aspath_string_mixed() {
3290 let p = AsPath {
3291 segments: vec![
3292 AsPathSegment::AsSequence(vec![65001, 65002]),
3293 AsPathSegment::AsSet(vec![65003, 65004]),
3294 ],
3295 };
3296 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3297 }
3298
3299 #[test]
3300 fn aspath_string_empty() {
3301 let p = AsPath { segments: vec![] };
3302 assert_eq!(p.to_aspath_string(), "");
3303 }
3304
3305 #[test]
3310 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3311 let bytes = vec![
3314 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3320 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3321 match err {
3322 DecodeError::MalformedField { detail, .. } => {
3323 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3324 }
3325 other => panic!("expected MalformedField, got {other:?}"),
3326 }
3327 }
3328
3329 #[test]
3330 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3331 let bytes = vec![
3332 0x00, 0x02, 70, 3, 0, ];
3336 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3337 match err {
3338 DecodeError::MalformedField { detail, .. } => {
3339 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3340 }
3341 other => panic!("expected MalformedField, got {other:?}"),
3342 }
3343 }
3344
3345 #[test]
3346 fn pmsi_tunnel_path_attribute_round_trips_through_dispatch() {
3347 let pmsi =
3352 crate::pmsi::PmsiTunnel::for_evpn_ingress_replication(100, "10.0.0.1".parse().unwrap());
3353 let attrs = vec![
3354 PathAttribute::Origin(Origin::Igp),
3355 PathAttribute::AsPath(AsPath { segments: vec![] }),
3356 PathAttribute::LocalPref(100),
3357 PathAttribute::PmsiTunnel(pmsi.clone()),
3358 ];
3359
3360 let mut buf = Vec::new();
3361 encode_path_attributes(&attrs, &mut buf, true, false);
3362 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3363
3364 assert_eq!(decoded, attrs);
3365
3366 let pmsi_decoded = decoded
3369 .iter()
3370 .find_map(|a| match a {
3371 PathAttribute::PmsiTunnel(p) => Some(p),
3372 _ => None,
3373 })
3374 .expect("PMSI present");
3375 assert_eq!(pmsi_decoded, &pmsi);
3376 assert_eq!(
3377 PathAttribute::PmsiTunnel(pmsi).flags(),
3378 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3379 );
3380 }
3381
3382 #[test]
3383 fn pmsi_tunnel_decode_attribute_with_truncated_value_is_malformed() {
3384 let buf = [
3386 0xC0, 22, 0x04, 0x00, 0x06, 0x00, 0x00,
3390 ];
3391 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3392 assert!(matches!(err, DecodeError::MalformedField { .. }));
3393 }
3394}