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 OnlyToCustomer(u32),
639 Unknown(RawAttribute),
641}
642
643impl PathAttribute {
644 #[must_use]
646 pub fn type_code(&self) -> u8 {
647 match self {
648 Self::Origin(_) => attr_type::ORIGIN,
649 Self::AsPath(_) => attr_type::AS_PATH,
650 Self::NextHop(_) => attr_type::NEXT_HOP,
651 Self::LocalPref(_) => attr_type::LOCAL_PREF,
652 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
653 Self::Communities(_) => attr_type::COMMUNITIES,
654 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
655 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
656 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
657 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
658 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
659 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
660 Self::PmsiTunnel(_) => attr_type::PMSI_TUNNEL,
661 Self::OnlyToCustomer(_) => attr_type::ONLY_TO_CUSTOMER,
662 Self::Unknown(raw) => raw.type_code,
663 }
664 }
665
666 #[must_use]
668 pub fn flags(&self) -> u8 {
669 match self {
670 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
671 attr_flags::TRANSITIVE
672 }
673 Self::Med(_)
674 | Self::OriginatorId(_)
675 | Self::ClusterList(_)
676 | Self::MpReachNlri(_)
677 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
678 Self::Communities(_)
679 | Self::ExtendedCommunities(_)
680 | Self::LargeCommunities(_)
681 | Self::PmsiTunnel(_)
682 | Self::OnlyToCustomer(_) => attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
683 Self::Unknown(raw) => raw.flags,
684 }
685 }
686}
687
688#[derive(Debug, Clone, PartialEq, Eq, Hash)]
693pub struct RawAttribute {
694 pub flags: u8,
696 pub type_code: u8,
698 pub data: Bytes,
700}
701
702pub fn decode_path_attributes(
713 mut buf: &[u8],
714 four_octet_as: bool,
715 add_path_families: &[(Afi, Safi)],
716) -> Result<Vec<PathAttribute>, DecodeError> {
717 let mut attrs = Vec::new();
718
719 while !buf.is_empty() {
720 if buf.len() < 2 {
722 return Err(DecodeError::MalformedField {
723 message_type: "UPDATE",
724 detail: "truncated attribute header".to_string(),
725 });
726 }
727
728 let flags = buf[0];
729 let type_code = buf[1];
730 buf = &buf[2..];
731
732 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
733 let value_len = if extended {
734 if buf.len() < 2 {
735 return Err(DecodeError::MalformedField {
736 message_type: "UPDATE",
737 detail: "truncated extended-length attribute".to_string(),
738 });
739 }
740 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
741 buf = &buf[2..];
742 len
743 } else {
744 if buf.is_empty() {
745 return Err(DecodeError::MalformedField {
746 message_type: "UPDATE",
747 detail: "truncated attribute length".to_string(),
748 });
749 }
750 let len = buf[0] as usize;
751 buf = &buf[1..];
752 len
753 };
754
755 if buf.len() < value_len {
756 return Err(DecodeError::MalformedField {
757 message_type: "UPDATE",
758 detail: format!(
759 "attribute type {type_code} value truncated: need {value_len}, have {}",
760 buf.len()
761 ),
762 });
763 }
764
765 let value = &buf[..value_len];
766 buf = &buf[value_len..];
767
768 let attr =
769 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
770 attrs.push(attr);
771 }
772
773 Ok(attrs)
774}
775
776#[expect(clippy::too_many_lines)]
778fn decode_attribute_value(
779 flags: u8,
780 type_code: u8,
781 value: &[u8],
782 four_octet_as: bool,
783 add_path_families: &[(Afi, Safi)],
784) -> Result<PathAttribute, DecodeError> {
785 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
787 if let Some(expected) = expected_flags(type_code)
788 && (flags & flags_mask) != expected
789 {
790 return Err(DecodeError::UpdateAttributeError {
791 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
792 data: attr_error_data(flags, type_code, value),
793 detail: format!(
794 "type {} flags {:#04x} (expected {:#04x})",
795 type_code,
796 flags & flags_mask,
797 expected
798 ),
799 });
800 }
801
802 match type_code {
803 attr_type::ORIGIN => {
804 if value.len() != 1 {
805 return Err(DecodeError::UpdateAttributeError {
806 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
807 data: attr_error_data(flags, type_code, value),
808 detail: format!("ORIGIN length {} (expected 1)", value.len()),
809 });
810 }
811 match Origin::from_u8(value[0]) {
812 Some(origin) => Ok(PathAttribute::Origin(origin)),
813 None => Err(DecodeError::UpdateAttributeError {
814 subcode: update_subcode::INVALID_ORIGIN,
815 data: attr_error_data(flags, type_code, value),
816 detail: format!("invalid ORIGIN value {}", value[0]),
817 }),
818 }
819 }
820
821 attr_type::AS_PATH => {
822 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
823 DecodeError::UpdateAttributeError {
824 subcode: update_subcode::MALFORMED_AS_PATH,
825 data: attr_error_data(flags, type_code, value),
826 detail: e.to_string(),
827 }
828 })?;
829 Ok(PathAttribute::AsPath(AsPath { segments }))
830 }
831
832 attr_type::NEXT_HOP => {
833 if value.len() != 4 {
834 return Err(DecodeError::UpdateAttributeError {
835 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
836 data: attr_error_data(flags, type_code, value),
837 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
838 });
839 }
840 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
841 Ok(PathAttribute::NextHop(addr))
842 }
843
844 attr_type::MULTI_EXIT_DISC => {
845 if value.len() != 4 {
846 return Err(DecodeError::UpdateAttributeError {
847 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
848 data: attr_error_data(flags, type_code, value),
849 detail: format!("MED length {} (expected 4)", value.len()),
850 });
851 }
852 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
853 Ok(PathAttribute::Med(med))
854 }
855
856 attr_type::LOCAL_PREF => {
857 if value.len() != 4 {
858 return Err(DecodeError::UpdateAttributeError {
859 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
860 data: attr_error_data(flags, type_code, value),
861 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
862 });
863 }
864 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
865 Ok(PathAttribute::LocalPref(lp))
866 }
867
868 attr_type::COMMUNITIES => {
869 if !value.len().is_multiple_of(4) {
870 return Err(DecodeError::UpdateAttributeError {
871 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
872 data: attr_error_data(flags, type_code, value),
873 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
874 });
875 }
876 let communities = value
877 .chunks_exact(4)
878 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
879 .collect();
880 Ok(PathAttribute::Communities(communities))
881 }
882
883 attr_type::EXTENDED_COMMUNITIES => {
884 if !value.len().is_multiple_of(8) {
885 return Err(DecodeError::UpdateAttributeError {
886 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
887 data: attr_error_data(flags, type_code, value),
888 detail: format!(
889 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
890 value.len()
891 ),
892 });
893 }
894 let communities = value
895 .chunks_exact(8)
896 .map(|c| {
897 ExtendedCommunity::new(u64::from_be_bytes([
898 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
899 ]))
900 })
901 .collect();
902 Ok(PathAttribute::ExtendedCommunities(communities))
903 }
904
905 attr_type::ORIGINATOR_ID => {
906 if value.len() != 4 {
907 return Err(DecodeError::UpdateAttributeError {
908 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
909 data: attr_error_data(flags, type_code, value),
910 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
911 });
912 }
913 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
914 Ok(PathAttribute::OriginatorId(addr))
915 }
916
917 attr_type::CLUSTER_LIST => {
918 if !value.len().is_multiple_of(4) {
919 return Err(DecodeError::UpdateAttributeError {
920 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
921 data: attr_error_data(flags, type_code, value),
922 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
923 });
924 }
925 let ids = value
926 .chunks_exact(4)
927 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
928 .collect();
929 Ok(PathAttribute::ClusterList(ids))
930 }
931
932 attr_type::LARGE_COMMUNITIES => {
933 if value.is_empty() || !value.len().is_multiple_of(12) {
934 return Err(DecodeError::UpdateAttributeError {
935 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
936 data: attr_error_data(flags, type_code, value),
937 detail: format!(
938 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
939 value.len()
940 ),
941 });
942 }
943 let communities = value
944 .chunks_exact(12)
945 .map(|c| {
946 LargeCommunity::new(
947 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
948 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
949 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
950 )
951 })
952 .collect();
953 Ok(PathAttribute::LargeCommunities(communities))
954 }
955
956 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
957 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
958
959 attr_type::PMSI_TUNNEL => {
960 let pmsi = crate::pmsi::PmsiTunnel::decode(value)?;
961 Ok(PathAttribute::PmsiTunnel(pmsi))
962 }
963
964 attr_type::ONLY_TO_CUSTOMER => {
965 if value.len() != 4 || (flags & attr_flags::PARTIAL) != 0 {
991 return Ok(PathAttribute::Unknown(RawAttribute {
992 flags,
993 type_code,
994 data: Bytes::copy_from_slice(value),
995 }));
996 }
997 let asn = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
998 Ok(PathAttribute::OnlyToCustomer(asn))
999 }
1000
1001 _ => Ok(PathAttribute::Unknown(RawAttribute {
1003 flags,
1004 type_code,
1005 data: Bytes::copy_from_slice(value),
1006 })),
1007 }
1008}
1009
1010#[expect(clippy::too_many_lines)]
1015fn decode_mp_reach_nlri(
1016 value: &[u8],
1017 add_path_families: &[(Afi, Safi)],
1018) -> Result<PathAttribute, DecodeError> {
1019 if value.len() < 5 {
1020 return Err(DecodeError::MalformedField {
1021 message_type: "UPDATE",
1022 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
1023 });
1024 }
1025
1026 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1027 let safi_raw = value[2];
1028 let nh_len = value[3] as usize;
1029
1030 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1031 message_type: "UPDATE",
1032 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
1033 })?;
1034 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1035 message_type: "UPDATE",
1036 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
1037 })?;
1038
1039 if value.len() < 4 + nh_len + 1 {
1041 return Err(DecodeError::MalformedField {
1042 message_type: "UPDATE",
1043 detail: format!(
1044 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
1045 value.len()
1046 ),
1047 });
1048 }
1049
1050 let nh_bytes = &value[4..4 + nh_len];
1051 let mut link_local_next_hop: Option<Ipv6Addr> = None;
1053 let next_hop = if safi == Safi::FlowSpec {
1054 if nh_len != 0 {
1055 return Err(DecodeError::MalformedField {
1056 message_type: "UPDATE",
1057 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
1058 });
1059 }
1060 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
1061 } else {
1062 match afi {
1063 Afi::Ipv4 => match nh_len {
1064 4 => IpAddr::V4(Ipv4Addr::new(
1065 nh_bytes[0],
1066 nh_bytes[1],
1067 nh_bytes[2],
1068 nh_bytes[3],
1069 )),
1070 16 | 32 => {
1071 let mut octets = [0u8; 16];
1072 octets.copy_from_slice(&nh_bytes[..16]);
1073 if nh_len == 32 {
1074 let mut ll = [0u8; 16];
1075 ll.copy_from_slice(&nh_bytes[16..32]);
1076 link_local_next_hop = Some(Ipv6Addr::from(ll));
1077 }
1078 IpAddr::V6(Ipv6Addr::from(octets))
1079 }
1080 _ => {
1081 return Err(DecodeError::MalformedField {
1082 message_type: "UPDATE",
1083 detail: format!(
1084 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
1085 ),
1086 });
1087 }
1088 },
1089 Afi::Ipv6 => {
1090 if nh_len != 16 && nh_len != 32 {
1091 return Err(DecodeError::MalformedField {
1092 message_type: "UPDATE",
1093 detail: format!(
1094 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
1095 ),
1096 });
1097 }
1098 let mut octets = [0u8; 16];
1099 octets.copy_from_slice(&nh_bytes[..16]);
1100 if nh_len == 32 {
1101 let mut ll = [0u8; 16];
1102 ll.copy_from_slice(&nh_bytes[16..32]);
1103 link_local_next_hop = Some(Ipv6Addr::from(ll));
1104 }
1105 IpAddr::V6(Ipv6Addr::from(octets))
1106 }
1107 Afi::L2Vpn => match nh_len {
1108 4 => IpAddr::V4(Ipv4Addr::new(
1109 nh_bytes[0],
1110 nh_bytes[1],
1111 nh_bytes[2],
1112 nh_bytes[3],
1113 )),
1114 16 => {
1115 let mut octets = [0u8; 16];
1116 octets.copy_from_slice(&nh_bytes[..16]);
1117 IpAddr::V6(Ipv6Addr::from(octets))
1118 }
1119 _ => {
1120 return Err(DecodeError::MalformedField {
1121 message_type: "UPDATE",
1122 detail: format!(
1123 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
1124 ),
1125 });
1126 }
1127 },
1128 }
1129 };
1130
1131 let nlri_start = 4 + nh_len + 1;
1133 let nlri_bytes = &value[nlri_start..];
1134
1135 if safi == Safi::FlowSpec {
1137 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1138 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1139 afi,
1140 safi,
1141 next_hop,
1142 link_local_next_hop,
1143 announced: vec![],
1144 flowspec_announced: flowspec_rules,
1145 evpn_announced: vec![],
1146 }));
1147 }
1148
1149 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1151 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1152 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1153 afi,
1154 safi,
1155 next_hop,
1156 link_local_next_hop,
1157 announced: vec![],
1158 flowspec_announced: vec![],
1159 evpn_announced: routes,
1160 }));
1161 }
1162
1163 if safi == Safi::Evpn {
1167 return Err(DecodeError::MalformedField {
1168 message_type: "UPDATE",
1169 detail: format!(
1170 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1171 afi as u16
1172 ),
1173 });
1174 }
1175
1176 let add_path = add_path_families.contains(&(afi, safi));
1177 let announced = match (afi, add_path) {
1178 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1179 .into_iter()
1180 .map(|p| NlriEntry {
1181 path_id: 0,
1182 prefix: Prefix::V4(p),
1183 })
1184 .collect(),
1185 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1186 .into_iter()
1187 .map(|e| NlriEntry {
1188 path_id: e.path_id,
1189 prefix: Prefix::V4(e.prefix),
1190 })
1191 .collect(),
1192 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1193 .into_iter()
1194 .map(|p| NlriEntry {
1195 path_id: 0,
1196 prefix: Prefix::V6(p),
1197 })
1198 .collect(),
1199 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1200 (Afi::L2Vpn, _) => {
1201 return Err(DecodeError::MalformedField {
1202 message_type: "UPDATE",
1203 detail: format!(
1204 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1205 safi as u8
1206 ),
1207 });
1208 }
1209 };
1210
1211 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1212 afi,
1213 safi,
1214 next_hop,
1215 link_local_next_hop,
1216 announced,
1217 flowspec_announced: vec![],
1218 evpn_announced: vec![],
1219 }))
1220}
1221
1222fn decode_mp_unreach_nlri(
1227 value: &[u8],
1228 add_path_families: &[(Afi, Safi)],
1229) -> Result<PathAttribute, DecodeError> {
1230 if value.len() < 3 {
1231 return Err(DecodeError::MalformedField {
1232 message_type: "UPDATE",
1233 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1234 });
1235 }
1236
1237 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1238 let safi_raw = value[2];
1239
1240 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1241 message_type: "UPDATE",
1242 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1243 })?;
1244 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1245 message_type: "UPDATE",
1246 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1247 })?;
1248
1249 let withdrawn_bytes = &value[3..];
1250
1251 if safi == Safi::FlowSpec {
1253 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1254 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1255 afi,
1256 safi,
1257 withdrawn: vec![],
1258 flowspec_withdrawn: flowspec_rules,
1259 evpn_withdrawn: vec![],
1260 }));
1261 }
1262
1263 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1265 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1266 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1267 afi,
1268 safi,
1269 withdrawn: vec![],
1270 flowspec_withdrawn: vec![],
1271 evpn_withdrawn: routes,
1272 }));
1273 }
1274
1275 if safi == Safi::Evpn {
1279 return Err(DecodeError::MalformedField {
1280 message_type: "UPDATE",
1281 detail: format!(
1282 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1283 afi as u16
1284 ),
1285 });
1286 }
1287
1288 let add_path = add_path_families.contains(&(afi, safi));
1289 let withdrawn = match (afi, add_path) {
1290 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1291 .into_iter()
1292 .map(|p| NlriEntry {
1293 path_id: 0,
1294 prefix: Prefix::V4(p),
1295 })
1296 .collect(),
1297 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1298 .into_iter()
1299 .map(|e| NlriEntry {
1300 path_id: e.path_id,
1301 prefix: Prefix::V4(e.prefix),
1302 })
1303 .collect(),
1304 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1305 .into_iter()
1306 .map(|p| NlriEntry {
1307 path_id: 0,
1308 prefix: Prefix::V6(p),
1309 })
1310 .collect(),
1311 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1312 (Afi::L2Vpn, _) => {
1313 return Err(DecodeError::MalformedField {
1314 message_type: "UPDATE",
1315 detail: format!(
1316 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1317 safi as u8
1318 ),
1319 });
1320 }
1321 };
1322
1323 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1324 afi,
1325 safi,
1326 withdrawn,
1327 flowspec_withdrawn: vec![],
1328 evpn_withdrawn: vec![],
1329 }))
1330}
1331
1332fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1334 let as_size: usize = if four_octet_as { 4 } else { 2 };
1335 let mut segments = Vec::new();
1336
1337 while !buf.is_empty() {
1338 if buf.len() < 2 {
1339 return Err(DecodeError::MalformedField {
1340 message_type: "UPDATE",
1341 detail: "truncated AS_PATH segment header".to_string(),
1342 });
1343 }
1344
1345 let seg_type = buf[0];
1346 let seg_count = buf[1] as usize;
1347 buf = &buf[2..];
1348
1349 let needed = seg_count * as_size;
1350 if buf.len() < needed {
1351 return Err(DecodeError::MalformedField {
1352 message_type: "UPDATE",
1353 detail: format!(
1354 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1355 buf.len()
1356 ),
1357 });
1358 }
1359
1360 let mut asns = Vec::with_capacity(seg_count);
1361 for _ in 0..seg_count {
1362 let asn = if four_octet_as {
1363 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1364 buf = &buf[4..];
1365 v
1366 } else {
1367 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1368 buf = &buf[2..];
1369 v
1370 };
1371 asns.push(asn);
1372 }
1373
1374 match seg_type {
1375 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1376 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1377 _ => {
1378 return Err(DecodeError::MalformedField {
1379 message_type: "UPDATE",
1380 detail: format!("unknown AS_PATH segment type {seg_type}"),
1381 });
1382 }
1383 }
1384 }
1385
1386 Ok(segments)
1387}
1388
1389pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1392 let mut buf = Vec::with_capacity(3 + value.len());
1393 if value.len() > 255 {
1394 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1395 buf.push(type_code);
1396 #[expect(clippy::cast_possible_truncation)]
1397 let len = value.len() as u16;
1398 buf.extend_from_slice(&len.to_be_bytes());
1399 } else {
1400 buf.push(flags);
1401 buf.push(type_code);
1402 #[expect(clippy::cast_possible_truncation)]
1403 buf.push(value.len() as u8);
1404 }
1405 buf.extend_from_slice(value);
1406 buf
1407}
1408
1409fn expected_flags(type_code: u8) -> Option<u8> {
1412 match type_code {
1413 attr_type::ORIGIN
1415 | attr_type::AS_PATH
1416 | attr_type::NEXT_HOP
1417 | attr_type::LOCAL_PREF
1418 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1419 attr_type::MULTI_EXIT_DISC
1422 | attr_type::ORIGINATOR_ID
1423 | attr_type::CLUSTER_LIST
1424 | attr_type::MP_REACH_NLRI
1425 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1426 attr_type::AGGREGATOR
1428 | attr_type::COMMUNITIES
1429 | attr_type::EXTENDED_COMMUNITIES
1430 | attr_type::LARGE_COMMUNITIES
1431 | attr_type::PMSI_TUNNEL
1432 | attr_type::ONLY_TO_CUSTOMER => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1433 _ => None,
1434 }
1435}
1436
1437#[expect(
1445 clippy::too_many_lines,
1446 reason = "dispatch arms are inherently O(variants); each new path attribute adds a small block"
1447)]
1448pub fn encode_path_attributes(
1449 attrs: &[PathAttribute],
1450 buf: &mut Vec<u8>,
1451 four_octet_as: bool,
1452 add_path_mp: bool,
1453) {
1454 for attr in attrs {
1455 let mut value = Vec::new();
1456 let flags;
1457 let type_code;
1458
1459 match attr {
1460 PathAttribute::Origin(origin) => {
1461 flags = attr_flags::TRANSITIVE;
1462 type_code = attr_type::ORIGIN;
1463 value.push(*origin as u8);
1464 }
1465 PathAttribute::AsPath(as_path) => {
1466 flags = attr_flags::TRANSITIVE;
1467 type_code = attr_type::AS_PATH;
1468 encode_as_path(as_path, &mut value, four_octet_as);
1469 }
1470 PathAttribute::NextHop(addr) => {
1471 flags = attr_flags::TRANSITIVE;
1472 type_code = attr_type::NEXT_HOP;
1473 value.extend_from_slice(&addr.octets());
1474 }
1475 PathAttribute::Med(med) => {
1476 flags = attr_flags::OPTIONAL;
1477 type_code = attr_type::MULTI_EXIT_DISC;
1478 value.extend_from_slice(&med.to_be_bytes());
1479 }
1480 PathAttribute::LocalPref(lp) => {
1481 flags = attr_flags::TRANSITIVE;
1482 type_code = attr_type::LOCAL_PREF;
1483 value.extend_from_slice(&lp.to_be_bytes());
1484 }
1485 PathAttribute::Communities(communities) => {
1486 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1487 type_code = attr_type::COMMUNITIES;
1488 for &c in communities {
1489 value.extend_from_slice(&c.to_be_bytes());
1490 }
1491 }
1492 PathAttribute::ExtendedCommunities(communities) => {
1493 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1494 type_code = attr_type::EXTENDED_COMMUNITIES;
1495 for &c in communities {
1496 value.extend_from_slice(&c.as_u64().to_be_bytes());
1497 }
1498 }
1499 PathAttribute::LargeCommunities(communities) => {
1500 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1501 type_code = attr_type::LARGE_COMMUNITIES;
1502 for &c in communities {
1503 value.extend_from_slice(&c.global_admin.to_be_bytes());
1504 value.extend_from_slice(&c.local_data1.to_be_bytes());
1505 value.extend_from_slice(&c.local_data2.to_be_bytes());
1506 }
1507 }
1508 PathAttribute::OriginatorId(addr) => {
1509 flags = attr_flags::OPTIONAL;
1510 type_code = attr_type::ORIGINATOR_ID;
1511 value.extend_from_slice(&addr.octets());
1512 }
1513 PathAttribute::ClusterList(ids) => {
1514 flags = attr_flags::OPTIONAL;
1515 type_code = attr_type::CLUSTER_LIST;
1516 for id in ids {
1517 value.extend_from_slice(&id.octets());
1518 }
1519 }
1520 PathAttribute::MpReachNlri(mp) => {
1521 flags = attr_flags::OPTIONAL;
1522 type_code = attr_type::MP_REACH_NLRI;
1523 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1524 }
1525 PathAttribute::MpUnreachNlri(mp) => {
1526 flags = attr_flags::OPTIONAL;
1527 type_code = attr_type::MP_UNREACH_NLRI;
1528 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1529 }
1530 PathAttribute::PmsiTunnel(pmsi) => {
1531 (flags, type_code) = (
1533 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
1534 attr_type::PMSI_TUNNEL,
1535 );
1536 pmsi.encode(&mut value);
1537 }
1538 PathAttribute::OnlyToCustomer(asn) => {
1539 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1544 type_code = attr_type::ONLY_TO_CUSTOMER;
1545 value.extend_from_slice(&asn.to_be_bytes());
1546 }
1547 PathAttribute::Unknown(raw) => {
1548 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1552 flags = if (raw.flags & optional_transitive) == optional_transitive {
1553 raw.flags | attr_flags::PARTIAL
1554 } else {
1555 raw.flags
1556 };
1557 type_code = raw.type_code;
1558 value.extend_from_slice(&raw.data);
1559 }
1560 }
1561
1562 if value.len() > 255 {
1564 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1565 buf.push(type_code);
1566 #[expect(clippy::cast_possible_truncation)]
1567 let len = value.len() as u16;
1568 buf.extend_from_slice(&len.to_be_bytes());
1569 } else {
1570 buf.push(flags);
1571 buf.push(type_code);
1572 #[expect(clippy::cast_possible_truncation)]
1573 buf.push(value.len() as u8);
1574 }
1575 buf.extend_from_slice(&value);
1576 }
1577}
1578
1579fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1584 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1585 buf.push(mp.safi as u8);
1586
1587 if mp.safi == Safi::FlowSpec {
1589 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1592 return;
1593 }
1594
1595 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1597 match mp.next_hop {
1598 IpAddr::V4(addr) => {
1599 buf.push(4);
1600 buf.extend_from_slice(&addr.octets());
1601 }
1602 IpAddr::V6(addr) => {
1603 buf.push(16);
1604 buf.extend_from_slice(&addr.octets());
1605 }
1606 }
1607 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1609 return;
1610 }
1611
1612 match (mp.next_hop, mp.link_local_next_hop) {
1613 (IpAddr::V4(addr), _) => {
1614 buf.push(4); buf.extend_from_slice(&addr.octets());
1616 }
1617 (IpAddr::V6(addr), Some(ll)) => {
1618 debug_assert!(
1630 (ll.segments()[0] & 0xffc0) == 0xfe80,
1631 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1632 );
1633 buf.push(32); buf.extend_from_slice(&addr.octets());
1635 buf.extend_from_slice(&ll.octets());
1636 }
1637 (IpAddr::V6(addr), None) => {
1638 buf.push(16); buf.extend_from_slice(&addr.octets());
1640 }
1641 }
1642
1643 buf.push(0); if add_path {
1646 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1647 } else {
1648 for entry in &mp.announced {
1649 match entry.prefix {
1650 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1651 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1652 }
1653 }
1654 }
1655}
1656
1657fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1661 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1662 buf.push(mp.safi as u8);
1663
1664 if mp.safi == Safi::FlowSpec {
1666 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1667 return;
1668 }
1669
1670 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1672 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1673 return;
1674 }
1675
1676 if add_path {
1677 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1678 } else {
1679 for entry in &mp.withdrawn {
1680 match entry.prefix {
1681 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1682 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1683 }
1684 }
1685 }
1686}
1687
1688fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1690 for segment in &as_path.segments {
1691 let (seg_type, asns) = match segment {
1692 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1693 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1694 };
1695 for chunk in asns.chunks(u8::MAX as usize) {
1696 buf.push(seg_type);
1697 #[expect(clippy::cast_possible_truncation)]
1698 buf.push(chunk.len() as u8);
1699 for &asn in chunk {
1700 if four_octet_as {
1701 buf.extend_from_slice(&asn.to_be_bytes());
1702 } else {
1703 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1706 buf.extend_from_slice(&as2.to_be_bytes());
1707 }
1708 }
1709 }
1710 }
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715 use super::*;
1716
1717 #[test]
1718 fn mp_reach_evpn_attribute_roundtrip() {
1719 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1720
1721 let mp = MpReachNlri {
1722 afi: Afi::L2Vpn,
1723 safi: Safi::Evpn,
1724 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1725 link_local_next_hop: None,
1726 announced: vec![],
1727 flowspec_announced: vec![],
1728 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1729 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1730 ethernet_tag: EthernetTagId(100),
1731 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1732 })],
1733 };
1734 let attr = PathAttribute::MpReachNlri(mp);
1735
1736 let mut buf = Vec::new();
1737 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1738 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1739 assert_eq!(decoded.len(), 1);
1740 assert_eq!(attr, decoded[0]);
1741
1742 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1743 panic!("not MP_REACH after decode");
1744 };
1745 assert_eq!(dec.afi, Afi::L2Vpn);
1746 assert_eq!(dec.safi, Safi::Evpn);
1747 assert_eq!(dec.evpn_announced.len(), 1);
1748 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1749 }
1750
1751 #[test]
1762 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1763 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1764
1765 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1766 let mp = MpReachNlri {
1767 afi: Afi::L2Vpn,
1768 safi: Safi::Evpn,
1769 next_hop: IpAddr::V6(vtep_v6),
1770 link_local_next_hop: None,
1771 announced: vec![],
1772 flowspec_announced: vec![],
1773 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1774 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1775 ethernet_tag: EthernetTagId(100),
1776 originator_ip: IpAddr::V6(vtep_v6),
1777 })],
1778 };
1779 let attr = PathAttribute::MpReachNlri(mp.clone());
1780
1781 let mut buf = Vec::new();
1782 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1783
1784 let extended = (buf[0] & 0x10) != 0;
1795 let value_off = if extended { 4 } else { 3 };
1796 assert_eq!(
1797 buf[value_off + 3],
1798 16,
1799 "EVPN IPv6 NH-Len must be 16, not 32"
1800 );
1801 assert_eq!(
1802 &buf[value_off + 4..value_off + 20],
1803 &vtep_v6.octets(),
1804 "encoded VTEP next-hop bytes must match the input"
1805 );
1806
1807 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1808 assert_eq!(decoded.len(), 1);
1809 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1810
1811 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1812 panic!("not MP_REACH after decode");
1813 };
1814 assert_eq!(dec.afi, Afi::L2Vpn);
1815 assert_eq!(dec.safi, Safi::Evpn);
1816 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1817 assert!(
1818 dec.link_local_next_hop.is_none(),
1819 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1820 );
1821 assert_eq!(dec.evpn_announced.len(), 1);
1822 match &dec.evpn_announced[0] {
1823 EvpnRoute::Imet(imet) => {
1824 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1825 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1826 }
1827 other => panic!("expected IMET, got {other:?}"),
1828 }
1829 }
1830
1831 #[test]
1838 fn mp_reach_evpn_rejects_32byte_next_hop() {
1839 let mut attr = vec![0x80u8, 14, 37];
1845 attr.extend_from_slice(&[
1846 0x00, 0x19, 0x46, 0x20, ]);
1850 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1854 match err {
1855 DecodeError::MalformedField { detail, .. } => {
1856 assert!(
1857 detail.contains("L2VPN next-hop length 32"),
1858 "expected L2VPN NH-Len rejection, got: {detail}"
1859 );
1860 }
1861 other => panic!("expected MalformedField, got: {other:?}"),
1862 }
1863 }
1864
1865 #[test]
1866 fn mp_unreach_evpn_attribute_roundtrip() {
1867 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1868
1869 let mp = MpUnreachNlri {
1870 afi: Afi::L2Vpn,
1871 safi: Safi::Evpn,
1872 withdrawn: vec![],
1873 flowspec_withdrawn: vec![],
1874 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1875 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1876 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1877 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1878 })],
1879 };
1880 let attr = PathAttribute::MpUnreachNlri(mp);
1881 let mut buf = Vec::new();
1882 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1883 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1884 assert_eq!(decoded.len(), 1);
1885 assert_eq!(attr, decoded[0]);
1886 }
1887
1888 #[test]
1891 fn ext_comm_bgp_encapsulation_vxlan() {
1892 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1894 assert_eq!(c.subtype(), 0x0C);
1895 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1896 let b = c.as_u64().to_be_bytes();
1898 assert_eq!(b[2..6], [0, 0, 0, 0]);
1899 assert_eq!(&b[6..8], &[0, 8]);
1900 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1902 }
1903
1904 #[test]
1905 fn ext_comm_mac_mobility_sticky_and_sequence() {
1906 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1907 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1908 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1909 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1910 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1912 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1913 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1914 }
1915
1916 #[test]
1917 fn ext_comm_esi_label_flags_and_label() {
1918 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1919 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1920 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1921 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1922 }
1923
1924 #[test]
1925 fn ext_comm_es_import_rt_mac() {
1926 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1927 let e = ExtendedCommunity::es_import_rt(mac);
1928 assert_eq!(e.as_es_import_rt(), Some(mac));
1929 assert_eq!(e.type_byte(), 0x06);
1930 assert_eq!(e.subtype(), 0x02);
1931 }
1932
1933 #[test]
1934 fn ext_comm_df_election_hrw_roundtrips_reserved_bytes_zero() {
1935 let ec = ExtendedCommunity::df_election(1, 0, None);
1936 assert_eq!(ec.type_byte(), 0x06);
1937 assert_eq!(ec.subtype(), 0x06);
1938 assert_eq!(
1939 ec.as_df_election(),
1940 Some(DfElectionExtendedCommunity {
1941 algorithm_id: 1,
1942 capabilities: 0,
1943 preference: None,
1944 })
1945 );
1946 assert_eq!(ec.as_u64().to_be_bytes(), [0x06, 0x06, 0x01, 0, 0, 0, 0, 0]);
1947 }
1948
1949 #[test]
1950 fn ext_comm_df_election_preference_bytes_decode_for_rfc9785_algorithms() {
1951 let ec = ExtendedCommunity::df_election(3, 0x8000, Some(42));
1952 assert_eq!(
1953 ec.as_df_election(),
1954 Some(DfElectionExtendedCommunity {
1955 algorithm_id: 3,
1956 capabilities: 0x8000,
1957 preference: Some(42),
1958 })
1959 );
1960 }
1961
1962 #[test]
1963 fn ext_comm_router_mac() {
1964 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1965 let e = ExtendedCommunity::router_mac(mac);
1966 assert_eq!(e.as_router_mac(), Some(mac));
1967 }
1968
1969 #[test]
1970 fn ext_comm_link_bandwidth_roundtrips() {
1971 let bw = 1.25e9_f32; let e = ExtendedCommunity::link_bandwidth(65001, bw);
1973 assert_eq!(e.type_byte(), 0x40, "non-transitive two-octet-AS-specific");
1974 assert_eq!(e.subtype(), 0x04, "Link Bandwidth subtype");
1975 let (asn, decoded) = e.as_link_bandwidth().expect("decodes as link bandwidth");
1976 assert_eq!(asn, 65001);
1977 assert_eq!(decoded.to_bits(), bw.to_bits());
1979 }
1980
1981 #[test]
1982 fn ext_comm_link_bandwidth_decodes_known_wire_bytes() {
1983 let one = 1.0_f32.to_be_bytes();
1985 let raw = u64::from_be_bytes([0x40, 0x04, 0xFD, 0xE9, one[0], one[1], one[2], one[3]]);
1986 let (asn, bw) = ExtendedCommunity::new(raw)
1987 .as_link_bandwidth()
1988 .expect("decodes as link bandwidth");
1989 assert_eq!(asn, 65001);
1990 assert_eq!(bw.to_bits(), 1.0_f32.to_bits());
1991 }
1992
1993 #[test]
1994 fn ext_comm_link_bandwidth_rejects_wrong_type_or_subtype() {
1995 let transitive = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x04, 0, 0, 0, 0, 0, 0]));
1997 assert!(transitive.as_link_bandwidth().is_none());
1998 let wrong_sub = ExtendedCommunity::new(u64::from_be_bytes([0x40, 0x02, 0, 0, 0, 0, 0, 0]));
2000 assert!(wrong_sub.as_link_bandwidth().is_none());
2001 }
2002
2003 #[test]
2004 fn ext_comm_default_gateway_flag_only() {
2005 let d = ExtendedCommunity::default_gateway();
2006 assert!(d.as_default_gateway());
2007 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
2009 }
2010
2011 #[test]
2015 fn ext_comm_default_gateway_rejects_nonzero_value() {
2016 let malformed =
2018 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
2019 assert!(
2020 !malformed.as_default_gateway(),
2021 "default-gateway accessor must require all-zero value bytes"
2022 );
2023 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
2025 }
2026
2027 #[test]
2028 fn ext_comm_accessors_return_none_on_unrelated_communities() {
2029 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
2031 assert_eq!(rt.as_mac_mobility(), None);
2032 assert_eq!(rt.as_esi_label(), None);
2033 assert_eq!(rt.as_es_import_rt(), None);
2034 assert_eq!(rt.as_router_mac(), None);
2035 assert!(rt.as_link_bandwidth().is_none());
2036 assert!(!rt.as_default_gateway());
2037 }
2038
2039 #[test]
2040 fn origin_from_u8_roundtrip() {
2041 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
2042 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
2043 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
2044 assert_eq!(Origin::from_u8(3), None);
2045 }
2046
2047 #[test]
2048 fn origin_ordering() {
2049 assert!(Origin::Igp < Origin::Egp);
2050 assert!(Origin::Egp < Origin::Incomplete);
2051 }
2052
2053 #[test]
2054 fn as_path_length_calculation() {
2055 let path = AsPath {
2056 segments: vec![
2057 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
2058 AsPathSegment::AsSet(vec![65004, 65005]),
2059 ],
2060 };
2061 assert_eq!(path.len(), 4);
2063 }
2064
2065 #[test]
2066 fn as_path_empty() {
2067 let path = AsPath { segments: vec![] };
2068 assert!(path.is_empty());
2069 assert_eq!(path.len(), 0);
2070 }
2071
2072 #[test]
2073 fn contains_asn_in_sequence() {
2074 let path = AsPath {
2075 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2076 };
2077 assert!(path.contains_asn(65002));
2078 assert!(!path.contains_asn(65004));
2079 }
2080
2081 #[test]
2082 fn contains_asn_in_set() {
2083 let path = AsPath {
2084 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
2085 };
2086 assert!(path.contains_asn(65005));
2087 assert!(!path.contains_asn(65001));
2088 }
2089
2090 #[test]
2091 fn contains_asn_multiple_segments() {
2092 let path = AsPath {
2093 segments: vec![
2094 AsPathSegment::AsSequence(vec![65001, 65002]),
2095 AsPathSegment::AsSet(vec![65003]),
2096 ],
2097 };
2098 assert!(path.contains_asn(65001));
2099 assert!(path.contains_asn(65003));
2100 assert!(!path.contains_asn(65004));
2101 }
2102
2103 #[test]
2104 fn contains_asn_empty_path() {
2105 let path = AsPath { segments: vec![] };
2106 assert!(!path.contains_asn(65001));
2107 }
2108
2109 #[test]
2110 fn is_private_asn_boundaries() {
2111 assert!(!is_private_asn(64_511));
2113 assert!(is_private_asn(64_512));
2114 assert!(is_private_asn(65_534));
2115 assert!(!is_private_asn(65_535));
2116
2117 assert!(!is_private_asn(4_199_999_999));
2119 assert!(is_private_asn(4_200_000_000));
2120 assert!(is_private_asn(4_294_967_294));
2121 assert!(!is_private_asn(4_294_967_295));
2122 }
2123
2124 #[test]
2125 fn all_private_empty_path_is_false() {
2126 let path = AsPath { segments: vec![] };
2127 assert!(!path.all_private());
2128 }
2129
2130 #[test]
2131 fn all_private_mixed_segments() {
2132 let path = AsPath {
2133 segments: vec![
2134 AsPathSegment::AsSet(vec![64_512, 65_000]),
2135 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
2136 ],
2137 };
2138 assert!(path.all_private());
2139
2140 let non_private = AsPath {
2141 segments: vec![
2142 AsPathSegment::AsSet(vec![64_512, 65_000]),
2143 AsPathSegment::AsSequence(vec![65_535]),
2144 ],
2145 };
2146 assert!(!non_private.all_private());
2147 }
2148
2149 #[test]
2150 fn decode_origin_igp() {
2151 let buf = [0x40, 0x01, 0x01, 0x00];
2153 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2154 assert_eq!(attrs.len(), 1);
2155 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2156 }
2157
2158 #[test]
2159 fn decode_origin_egp() {
2160 let buf = [0x40, 0x01, 0x01, 0x01];
2161 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2162 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
2163 }
2164
2165 #[test]
2166 fn decode_origin_invalid_value() {
2167 let buf = [0x40, 0x01, 0x01, 0x05];
2169 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2170 match &err {
2171 DecodeError::UpdateAttributeError { subcode, .. } => {
2172 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2173 }
2174 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2175 }
2176 }
2177
2178 #[test]
2179 fn decode_next_hop() {
2180 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
2182 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2183 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2184 }
2185
2186 #[test]
2187 fn decode_med() {
2188 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
2190 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2191 assert_eq!(attrs[0], PathAttribute::Med(100));
2192 }
2193
2194 #[test]
2195 fn decode_local_pref() {
2196 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
2198 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2199 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
2200 }
2201
2202 #[test]
2203 fn decode_as_path_4byte() {
2204 let buf = [
2207 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2212 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2213 assert_eq!(
2214 attrs[0],
2215 PathAttribute::AsPath(AsPath {
2216 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2217 })
2218 );
2219 }
2220
2221 #[test]
2222 fn decode_as_path_2byte() {
2223 let buf = [
2226 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2231 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2232 assert_eq!(
2233 attrs[0],
2234 PathAttribute::AsPath(AsPath {
2235 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2236 })
2237 );
2238 }
2239
2240 #[test]
2241 fn decode_unknown_attribute_preserved() {
2242 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2244 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2245 assert_eq!(
2246 attrs[0],
2247 PathAttribute::Unknown(RawAttribute {
2248 flags: 0xC0,
2249 type_code: 99,
2250 data: Bytes::from_static(&[1, 2, 3]),
2251 })
2252 );
2253 }
2254
2255 #[test]
2256 fn decode_atomic_aggregate_as_unknown() {
2257 let buf = [0x40, 0x06, 0x00];
2259 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2260 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2261 }
2262
2263 #[test]
2264 fn decode_extended_length() {
2265 let buf = [
2268 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2273 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2274 assert_eq!(
2275 attrs[0],
2276 PathAttribute::AsPath(AsPath {
2277 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2278 })
2279 );
2280 }
2281
2282 #[test]
2283 fn decode_multiple_attributes() {
2284 let mut buf = Vec::new();
2285 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2287 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2289 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2291
2292 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2293 assert_eq!(attrs.len(), 3);
2294 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2295 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2296 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2297 }
2298
2299 #[test]
2300 fn roundtrip_attributes_4byte() {
2301 let attrs = vec![
2302 PathAttribute::Origin(Origin::Igp),
2303 PathAttribute::AsPath(AsPath {
2304 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2305 }),
2306 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2307 PathAttribute::Med(100),
2308 PathAttribute::LocalPref(200),
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 roundtrip_attributes_2byte() {
2319 let attrs = vec![
2320 PathAttribute::Origin(Origin::Egp),
2321 PathAttribute::AsPath(AsPath {
2322 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2323 }),
2324 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2325 ];
2326
2327 let mut buf = Vec::new();
2328 encode_path_attributes(&attrs, &mut buf, false, false);
2329 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2330 assert_eq!(decoded, attrs);
2331 }
2332
2333 #[test]
2334 fn reject_truncated_attribute_header() {
2335 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2337 }
2338
2339 #[test]
2340 fn reject_truncated_attribute_value() {
2341 let buf = [0x40, 0x01, 0x01];
2343 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2344 }
2345
2346 #[test]
2347 fn reject_bad_origin_length() {
2348 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2350 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2351 }
2352
2353 #[test]
2354 fn as_path_with_set_and_sequence() {
2355 let attrs = vec![PathAttribute::AsPath(AsPath {
2357 segments: vec![
2358 AsPathSegment::AsSequence(vec![65001]),
2359 AsPathSegment::AsSet(vec![65002, 65003]),
2360 ],
2361 })];
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 decode_communities_single() {
2371 let community: u32 = (65001 << 16) | 0x0064;
2374 let bytes = community.to_be_bytes();
2375 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2376 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2377 assert_eq!(attrs.len(), 1);
2378 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2379 }
2380
2381 #[test]
2382 fn decode_communities_multiple() {
2383 let c1: u32 = (65001 << 16) | 0x0064;
2384 let c2: u32 = (65002 << 16) | 0x00C8;
2385 let b1 = c1.to_be_bytes();
2386 let b2 = c2.to_be_bytes();
2387 let buf = [
2388 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2389 ];
2390 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2391 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2392 }
2393
2394 #[test]
2395 fn decode_communities_empty() {
2396 let buf = [0xC0, 0x08, 0x00];
2398 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2399 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2400 }
2401
2402 #[test]
2403 fn decode_communities_odd_length_rejected() {
2404 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2406 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2407 }
2408
2409 #[test]
2410 fn communities_roundtrip() {
2411 let c1: u32 = (65001 << 16) | 0x0064;
2412 let c2: u32 = (65002 << 16) | 0x00C8;
2413 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2414
2415 let mut buf = Vec::new();
2416 encode_path_attributes(&attrs, &mut buf, true, false);
2417 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2418 assert_eq!(decoded, attrs);
2419 }
2420
2421 #[test]
2422 fn communities_type_code_and_flags() {
2423 let attr = PathAttribute::Communities(vec![]);
2424 assert_eq!(attr.type_code(), 8);
2425 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2426 }
2427
2428 #[test]
2431 fn decode_extended_communities_single() {
2432 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2434 let bytes = ec.as_u64().to_be_bytes();
2435 let buf = [
2436 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2437 bytes[7],
2438 ];
2439 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2440 assert_eq!(attrs.len(), 1);
2441 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2442 }
2443
2444 #[test]
2445 fn decode_extended_communities_multiple() {
2446 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2447 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2448 let b1 = ec1.as_u64().to_be_bytes();
2449 let b2 = ec2.as_u64().to_be_bytes();
2450 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2452 buf.extend_from_slice(&b2);
2453 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2454 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2455 }
2456
2457 #[test]
2458 fn decode_extended_communities_empty() {
2459 let buf = [0xC0, 0x10, 0x00];
2460 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2461 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2462 }
2463
2464 #[test]
2465 fn decode_extended_communities_bad_length() {
2466 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2468 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2469 }
2470
2471 #[test]
2472 fn extended_communities_roundtrip() {
2473 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2474 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2475 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2476
2477 let mut buf = Vec::new();
2478 encode_path_attributes(&attrs, &mut buf, true, false);
2479 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2480 assert_eq!(decoded, attrs);
2481 }
2482
2483 #[test]
2484 fn extended_communities_type_code_and_flags() {
2485 let attr = PathAttribute::ExtendedCommunities(vec![]);
2486 assert_eq!(attr.type_code(), 16);
2487 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2488 }
2489
2490 #[test]
2491 fn extended_community_type_subtype() {
2492 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2494 assert_eq!(ec.type_byte(), 0x00);
2495 assert_eq!(ec.subtype(), 0x02);
2496 assert!(ec.is_transitive());
2497 }
2498
2499 #[test]
2500 fn extended_community_route_target() {
2501 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2503 assert_eq!(ec.route_target(), Some((65001, 100)));
2504 assert_eq!(ec.route_origin(), None);
2505
2506 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2508 assert_eq!(ec4.route_target(), Some((65537, 200)));
2509
2510 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2513 let (g, l) = ec_ipv4.route_target().unwrap();
2514 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2516 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2518 }
2519
2520 #[test]
2521 fn extended_community_is_transitive() {
2522 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2524 assert!(t.is_transitive());
2525
2526 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2528 assert!(!nt.is_transitive());
2529 }
2530
2531 #[test]
2532 fn extended_community_display() {
2533 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2534 assert_eq!(rt.to_string(), "RT:65001:100");
2535
2536 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2537 assert_eq!(ro.to_string(), "RO:65001:100");
2538
2539 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2541 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2542
2543 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2545 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2546
2547 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2549 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2550
2551 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2553 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2554 }
2555
2556 #[test]
2557 fn unknown_attribute_roundtrip() {
2558 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2561 flags: 0xC0,
2562 type_code: 99,
2563 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2564 })];
2565
2566 let mut buf = Vec::new();
2567 encode_path_attributes(&attrs, &mut buf, true, false);
2568 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2569 assert_eq!(
2570 decoded,
2571 vec![PathAttribute::Unknown(RawAttribute {
2572 flags: 0xE0, type_code: 99,
2574 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2575 })]
2576 );
2577 }
2578
2579 #[test]
2580 fn origin_with_optional_flag_rejected() {
2581 let buf = [0xC0, 0x01, 0x01, 0x00];
2583 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2584 match &err {
2585 DecodeError::UpdateAttributeError { subcode, .. } => {
2586 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2587 }
2588 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2589 }
2590 }
2591
2592 #[test]
2593 fn med_with_transitive_flag_rejected() {
2594 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2596 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2597 match &err {
2598 DecodeError::UpdateAttributeError { subcode, .. } => {
2599 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2600 }
2601 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2602 }
2603 }
2604
2605 #[test]
2606 fn communities_without_optional_rejected() {
2607 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2609 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2610 match &err {
2611 DecodeError::UpdateAttributeError { subcode, .. } => {
2612 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2613 }
2614 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2615 }
2616 }
2617
2618 #[test]
2619 fn next_hop_length_error_subcode() {
2620 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2622 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2623 match &err {
2624 DecodeError::UpdateAttributeError { subcode, .. } => {
2625 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2626 }
2627 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2628 }
2629 }
2630
2631 #[test]
2632 fn invalid_origin_value_subcode() {
2633 let buf = [0x40, 0x01, 0x01, 0x05];
2635 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2636 match &err {
2637 DecodeError::UpdateAttributeError { subcode, .. } => {
2638 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2639 }
2640 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2641 }
2642 }
2643
2644 #[test]
2645 fn as_path_bad_segment_subcode() {
2646 let buf = [
2648 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2652 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2653 match &err {
2654 DecodeError::UpdateAttributeError { subcode, .. } => {
2655 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2656 }
2657 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2658 }
2659 }
2660
2661 #[test]
2662 fn encode_unknown_transitive_sets_partial() {
2663 let attr = PathAttribute::Unknown(RawAttribute {
2664 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2666 data: Bytes::from_static(&[1, 2]),
2667 });
2668 let mut buf = Vec::new();
2669 encode_path_attributes(&[attr], &mut buf, true, false);
2670 assert_eq!(
2672 buf[0],
2673 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2674 );
2675 }
2676
2677 #[test]
2678 fn encode_unknown_wellknown_transitive_no_partial() {
2679 let attr = PathAttribute::Unknown(RawAttribute {
2681 flags: attr_flags::TRANSITIVE, type_code: 99,
2683 data: Bytes::from_static(&[1, 2]),
2684 });
2685 let mut buf = Vec::new();
2686 encode_path_attributes(&[attr], &mut buf, true, false);
2687 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2688 }
2689
2690 #[test]
2691 fn encode_unknown_nontransitive_no_partial() {
2692 let attr = PathAttribute::Unknown(RawAttribute {
2693 flags: attr_flags::OPTIONAL, type_code: 99,
2695 data: Bytes::from_static(&[1, 2]),
2696 });
2697 let mut buf = Vec::new();
2698 encode_path_attributes(&[attr], &mut buf, true, false);
2699 assert_eq!(buf[0], attr_flags::OPTIONAL);
2701 }
2702
2703 fn nlri(prefix: Prefix) -> NlriEntry {
2707 NlriEntry { path_id: 0, prefix }
2708 }
2709
2710 #[test]
2711 fn mp_reach_nlri_ipv6_roundtrip() {
2712 use crate::capability::{Afi, Safi};
2713 use crate::nlri::{Ipv6Prefix, Prefix};
2714
2715 let mp = MpReachNlri {
2716 afi: Afi::Ipv6,
2717 safi: Safi::Unicast,
2718 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2719 link_local_next_hop: None,
2720 announced: vec![
2721 nlri(Prefix::V6(Ipv6Prefix::new(
2722 "2001:db8:1::".parse().unwrap(),
2723 48,
2724 ))),
2725 nlri(Prefix::V6(Ipv6Prefix::new(
2726 "2001:db8:2::".parse().unwrap(),
2727 48,
2728 ))),
2729 ],
2730 flowspec_announced: vec![],
2731 evpn_announced: vec![],
2732 };
2733 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2734
2735 let mut buf = Vec::new();
2736 encode_path_attributes(&attrs, &mut buf, true, false);
2737 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2738 assert_eq!(decoded.len(), 1);
2739 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2740 }
2741
2742 #[test]
2743 fn mp_unreach_nlri_ipv6_roundtrip() {
2744 use crate::capability::{Afi, Safi};
2745 use crate::nlri::{Ipv6Prefix, Prefix};
2746
2747 let mp = MpUnreachNlri {
2748 afi: Afi::Ipv6,
2749 safi: Safi::Unicast,
2750 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2751 "2001:db8:1::".parse().unwrap(),
2752 48,
2753 )))],
2754 flowspec_withdrawn: vec![],
2755 evpn_withdrawn: vec![],
2756 };
2757 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2758
2759 let mut buf = Vec::new();
2760 encode_path_attributes(&attrs, &mut buf, true, false);
2761 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2762 assert_eq!(decoded.len(), 1);
2763 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2764 }
2765
2766 #[test]
2767 fn mp_reach_nlri_ipv4_roundtrip() {
2768 use crate::capability::{Afi, Safi};
2769 use crate::nlri::Prefix;
2770
2771 let mp = MpReachNlri {
2772 afi: Afi::Ipv4,
2773 safi: Safi::Unicast,
2774 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2775 link_local_next_hop: None,
2776 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2777 Ipv4Addr::new(10, 1, 0, 0),
2778 16,
2779 )))],
2780 flowspec_announced: vec![],
2781 evpn_announced: vec![],
2782 };
2783 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2784
2785 let mut buf = Vec::new();
2786 encode_path_attributes(&attrs, &mut buf, true, false);
2787 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2788 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2789 }
2790
2791 #[test]
2792 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2793 use crate::capability::{Afi, Safi};
2794 use crate::nlri::Prefix;
2795
2796 let mp = MpReachNlri {
2797 afi: Afi::Ipv4,
2798 safi: Safi::Unicast,
2799 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2800 link_local_next_hop: None,
2801 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2802 Ipv4Addr::new(10, 1, 0, 0),
2803 16,
2804 )))],
2805 flowspec_announced: vec![],
2806 evpn_announced: vec![],
2807 };
2808 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2809
2810 let mut buf = Vec::new();
2811 encode_path_attributes(&attrs, &mut buf, true, false);
2812 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2813 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2814 }
2815
2816 #[test]
2817 fn mp_reach_nlri_type_code_and_flags() {
2818 use crate::capability::{Afi, Safi};
2819
2820 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2821 afi: Afi::Ipv6,
2822 safi: Safi::Unicast,
2823 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2824 link_local_next_hop: None,
2825 announced: vec![],
2826 flowspec_announced: vec![],
2827 evpn_announced: vec![],
2828 });
2829 assert_eq!(attr.type_code(), 14);
2830 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2832 }
2833
2834 #[test]
2835 fn mp_unreach_nlri_type_code_and_flags() {
2836 use crate::capability::{Afi, Safi};
2837
2838 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2839 afi: Afi::Ipv6,
2840 safi: Safi::Unicast,
2841 withdrawn: vec![],
2842 flowspec_withdrawn: vec![],
2843 evpn_withdrawn: vec![],
2844 });
2845 assert_eq!(attr.type_code(), 15);
2846 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2847 }
2848
2849 #[test]
2850 fn mp_reach_nlri_empty_nlri() {
2851 use crate::capability::{Afi, Safi};
2852
2853 let mp = MpReachNlri {
2854 afi: Afi::Ipv6,
2855 safi: Safi::Unicast,
2856 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2857 link_local_next_hop: None,
2858 announced: vec![],
2859 flowspec_announced: vec![],
2860 evpn_announced: vec![],
2861 };
2862 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2863
2864 let mut buf = Vec::new();
2865 encode_path_attributes(&attrs, &mut buf, true, false);
2866 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2867 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2868 }
2869
2870 #[test]
2871 fn mp_reach_nlri_bad_flags_rejected() {
2872 let mut value = Vec::new();
2876 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();
2883 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2886 buf.push(value.len() as u8);
2887 buf.extend_from_slice(&value);
2888
2889 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2890 assert!(matches!(
2891 err,
2892 DecodeError::UpdateAttributeError {
2893 subcode: 4, ..
2895 }
2896 ));
2897 }
2898
2899 #[test]
2902 #[expect(clippy::cast_possible_truncation)]
2903 fn mp_reach_nlri_ipv4_addpath_decode() {
2904 use crate::capability::{Afi, Safi};
2905 use crate::nlri::Prefix;
2906
2907 let mut value = Vec::new();
2910 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());
2917 value.push(16);
2918 value.extend_from_slice(&[10, 1]);
2919
2920 let mut buf = Vec::new();
2921 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2924 buf.extend_from_slice(&value);
2925
2926 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2928 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2929 panic!("expected MpReachNlri");
2930 };
2931 assert_eq!(mp.announced.len(), 1);
2932 assert_eq!(mp.announced[0].path_id, 42);
2933 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2934
2935 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2938 }
2939
2940 #[test]
2941 #[expect(clippy::cast_possible_truncation)]
2942 fn mp_reach_nlri_ipv6_addpath_decode() {
2943 use crate::capability::{Afi, Safi};
2944 use crate::nlri::{Ipv6Prefix, Prefix};
2945
2946 let mut value = Vec::new();
2948 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());
2952 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2955 value.push(48);
2956 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2957
2958 let mut buf = Vec::new();
2959 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2962 buf.extend_from_slice(&value);
2963
2964 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2965 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2966 panic!("expected MpReachNlri");
2967 };
2968 assert_eq!(mp.announced.len(), 1);
2969 assert_eq!(mp.announced[0].path_id, 99);
2970 assert_eq!(
2971 mp.announced[0].prefix,
2972 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2973 );
2974 }
2975
2976 #[test]
2977 #[expect(clippy::cast_possible_truncation)]
2978 fn mp_unreach_nlri_ipv6_addpath_decode() {
2979 use crate::capability::{Afi, Safi};
2980 use crate::nlri::{Ipv6Prefix, Prefix};
2981
2982 let mut value = Vec::new();
2984 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2988 value.push(48);
2989 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2990
2991 let mut buf = Vec::new();
2992 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2995 buf.extend_from_slice(&value);
2996
2997 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2998 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2999 panic!("expected MpUnreachNlri");
3000 };
3001 assert_eq!(mp.withdrawn.len(), 1);
3002 assert_eq!(mp.withdrawn[0].path_id, 7);
3003 assert_eq!(
3004 mp.withdrawn[0].prefix,
3005 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
3006 );
3007 }
3008
3009 #[test]
3010 fn mp_reach_addpath_only_applies_to_matching_family() {
3011 use crate::capability::{Afi, Safi};
3012 use crate::nlri::{Ipv6Prefix, Prefix};
3013
3014 let mp = MpReachNlri {
3016 afi: Afi::Ipv6,
3017 safi: Safi::Unicast,
3018 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
3019 link_local_next_hop: None,
3020 announced: vec![NlriEntry {
3021 path_id: 0,
3022 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
3023 }],
3024 flowspec_announced: vec![],
3025 evpn_announced: vec![],
3026 };
3027 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
3028
3029 let mut buf = Vec::new();
3030 encode_path_attributes(&attrs, &mut buf, true, false);
3031
3032 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
3034 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
3035 }
3036
3037 #[test]
3040 fn decode_originator_id() {
3041 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
3043 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3044 assert_eq!(
3045 attrs[0],
3046 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
3047 );
3048 }
3049
3050 #[test]
3055 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
3056 use crate::capability::{Afi, Safi};
3057 use crate::nlri::{Ipv6Prefix, Prefix};
3058 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
3059 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
3060 let mp = MpReachNlri {
3061 afi: Afi::Ipv6,
3062 safi: Safi::Unicast,
3063 next_hop: IpAddr::V6(global),
3064 link_local_next_hop: Some(link_local),
3065 announced: vec![NlriEntry {
3066 path_id: 0,
3067 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
3068 }],
3069 flowspec_announced: vec![],
3070 evpn_announced: vec![],
3071 };
3072 let attr = PathAttribute::MpReachNlri(mp.clone());
3073 let mut buf = Vec::new();
3074 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3075
3076 let extended = (buf[0] & 0x10) != 0;
3080 let value_off = if extended { 4 } else { 3 };
3081 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
3083 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
3084 assert_eq!(
3085 &buf[value_off + 20..value_off + 36],
3086 &link_local.octets(),
3087 "encoded link-local bytes must match the input"
3088 );
3089
3090 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3091 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
3092 panic!("expected MpReachNlri");
3093 };
3094 assert_eq!(dec.next_hop, IpAddr::V6(global));
3095 assert_eq!(dec.link_local_next_hop, Some(link_local));
3096 }
3097
3098 #[test]
3106 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
3107 let value: &[u8] = &[
3110 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
3117 ];
3118 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
3121 attr.extend_from_slice(value);
3122 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
3123 match err {
3124 DecodeError::MalformedField { detail, .. } => {
3125 assert!(
3126 detail.contains("FlowSpec next-hop length"),
3127 "expected FlowSpec NH-Len rejection, got: {detail}"
3128 );
3129 }
3130 other => panic!("expected MalformedField, got {other:?}"),
3131 }
3132 }
3133
3134 #[test]
3135 fn originator_id_roundtrip() {
3136 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
3137 let mut buf = Vec::new();
3138 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3139 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3140 assert_eq!(decoded, vec![attr]);
3141 }
3142
3143 #[test]
3144 fn originator_id_wrong_length() {
3145 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
3147 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3148 assert!(matches!(
3149 err,
3150 DecodeError::UpdateAttributeError {
3151 subcode: 5, ..
3153 }
3154 ));
3155 }
3156
3157 #[test]
3158 fn originator_id_wrong_flags() {
3159 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
3161 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3162 assert!(matches!(
3163 err,
3164 DecodeError::UpdateAttributeError {
3165 subcode: 4, ..
3167 }
3168 ));
3169 }
3170
3171 #[test]
3174 fn decode_cluster_list() {
3175 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
3177 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3178 assert_eq!(
3179 attrs[0],
3180 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
3181 );
3182 }
3183
3184 #[test]
3185 fn cluster_list_roundtrip() {
3186 let attr = PathAttribute::ClusterList(vec![
3187 Ipv4Addr::new(10, 0, 0, 1),
3188 Ipv4Addr::new(10, 0, 0, 2),
3189 ]);
3190 let mut buf = Vec::new();
3191 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3192 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3193 assert_eq!(decoded, vec![attr]);
3194 }
3195
3196 #[test]
3197 fn cluster_list_wrong_length() {
3198 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
3200 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3201 assert!(matches!(
3202 err,
3203 DecodeError::UpdateAttributeError {
3204 subcode: 5, ..
3206 }
3207 ));
3208 }
3209
3210 #[test]
3215 fn large_community_display() {
3216 let lc = LargeCommunity::new(65001, 100, 200);
3217 assert_eq!(lc.to_string(), "65001:100:200");
3218 }
3219
3220 #[test]
3221 fn large_community_type_code_and_flags() {
3222 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3223 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3224 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3225 }
3226
3227 #[test]
3228 fn decode_large_community_single() {
3229 let mut buf = vec![0xC0, 32, 12];
3231 buf.extend_from_slice(&65001u32.to_be_bytes());
3232 buf.extend_from_slice(&100u32.to_be_bytes());
3233 buf.extend_from_slice(&200u32.to_be_bytes());
3234 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3235 assert_eq!(attrs.len(), 1);
3236 assert_eq!(
3237 attrs[0],
3238 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3239 );
3240 }
3241
3242 #[test]
3243 fn decode_large_community_multiple() {
3244 let mut buf = vec![0xC0, 32, 24];
3246 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3247 buf.extend_from_slice(&g.to_be_bytes());
3248 buf.extend_from_slice(&l1.to_be_bytes());
3249 buf.extend_from_slice(&l2.to_be_bytes());
3250 }
3251 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3252 assert_eq!(
3253 attrs[0],
3254 PathAttribute::LargeCommunities(vec![
3255 LargeCommunity::new(65001, 100, 200),
3256 LargeCommunity::new(65002, 300, 400),
3257 ])
3258 );
3259 }
3260
3261 #[test]
3262 fn decode_large_community_bad_length() {
3263 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3265 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3266 assert!(matches!(
3267 err,
3268 DecodeError::UpdateAttributeError {
3269 subcode: 5, ..
3271 }
3272 ));
3273 }
3274
3275 #[test]
3276 fn decode_large_community_empty_rejected() {
3277 let buf = [0xC0, 32, 0];
3279 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3280 assert!(matches!(
3281 err,
3282 DecodeError::UpdateAttributeError {
3283 subcode: 5, ..
3285 }
3286 ));
3287 }
3288
3289 #[test]
3290 fn large_community_roundtrip() {
3291 let lcs = vec![
3292 LargeCommunity::new(65001, 100, 200),
3293 LargeCommunity::new(0, u32::MAX, 42),
3294 ];
3295 let attr = PathAttribute::LargeCommunities(lcs.clone());
3296 let mut buf = Vec::new();
3297 encode_path_attributes(&[attr], &mut buf, true, false);
3298 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3299 assert_eq!(decoded.len(), 1);
3300 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3301 }
3302
3303 #[test]
3304 fn large_community_expected_flags_validated() {
3305 let mut buf = vec![0x40, 32, 12];
3307 buf.extend_from_slice(&1u32.to_be_bytes());
3308 buf.extend_from_slice(&2u32.to_be_bytes());
3309 buf.extend_from_slice(&3u32.to_be_bytes());
3310 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3311 assert!(matches!(
3312 err,
3313 DecodeError::UpdateAttributeError {
3314 subcode: 4, ..
3316 }
3317 ));
3318 }
3319
3320 #[test]
3325 fn aspath_string_sequence() {
3326 let p = AsPath {
3327 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3328 };
3329 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3330 }
3331
3332 #[test]
3333 fn aspath_string_set() {
3334 let p = AsPath {
3335 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3336 };
3337 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3338 }
3339
3340 #[test]
3341 fn aspath_string_mixed() {
3342 let p = AsPath {
3343 segments: vec![
3344 AsPathSegment::AsSequence(vec![65001, 65002]),
3345 AsPathSegment::AsSet(vec![65003, 65004]),
3346 ],
3347 };
3348 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3349 }
3350
3351 #[test]
3352 fn aspath_string_empty() {
3353 let p = AsPath { segments: vec![] };
3354 assert_eq!(p.to_aspath_string(), "");
3355 }
3356
3357 #[test]
3362 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3363 let bytes = vec![
3366 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3372 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3373 match err {
3374 DecodeError::MalformedField { detail, .. } => {
3375 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3376 }
3377 other => panic!("expected MalformedField, got {other:?}"),
3378 }
3379 }
3380
3381 #[test]
3382 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3383 let bytes = vec![
3384 0x00, 0x02, 70, 3, 0, ];
3388 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3389 match err {
3390 DecodeError::MalformedField { detail, .. } => {
3391 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3392 }
3393 other => panic!("expected MalformedField, got {other:?}"),
3394 }
3395 }
3396
3397 #[test]
3398 fn pmsi_tunnel_path_attribute_round_trips_through_dispatch() {
3399 let pmsi =
3404 crate::pmsi::PmsiTunnel::for_evpn_ingress_replication(100, "10.0.0.1".parse().unwrap());
3405 let attrs = vec![
3406 PathAttribute::Origin(Origin::Igp),
3407 PathAttribute::AsPath(AsPath { segments: vec![] }),
3408 PathAttribute::LocalPref(100),
3409 PathAttribute::PmsiTunnel(pmsi.clone()),
3410 ];
3411
3412 let mut buf = Vec::new();
3413 encode_path_attributes(&attrs, &mut buf, true, false);
3414 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3415
3416 assert_eq!(decoded, attrs);
3417
3418 let pmsi_decoded = decoded
3421 .iter()
3422 .find_map(|a| match a {
3423 PathAttribute::PmsiTunnel(p) => Some(p),
3424 _ => None,
3425 })
3426 .expect("PMSI present");
3427 assert_eq!(pmsi_decoded, &pmsi);
3428 assert_eq!(
3429 PathAttribute::PmsiTunnel(pmsi).flags(),
3430 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3431 );
3432 }
3433
3434 #[test]
3435 fn pmsi_tunnel_decode_attribute_with_truncated_value_is_malformed() {
3436 let buf = [
3438 0xC0, 22, 0x04, 0x00, 0x06, 0x00, 0x00,
3442 ];
3443 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3444 assert!(matches!(err, DecodeError::MalformedField { .. }));
3445 }
3446
3447 #[test]
3450 fn only_to_customer_encode_decode_roundtrip() {
3451 for asn in [0u32, 65000, 65536, 4_200_000_000, u32::MAX] {
3452 let attrs = vec![PathAttribute::OnlyToCustomer(asn)];
3453 let mut buf = Vec::new();
3454 encode_path_attributes(&attrs, &mut buf, true, false);
3455 assert_eq!(buf[0], attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3457 assert_eq!(buf[1], attr_type::ONLY_TO_CUSTOMER);
3458 assert_eq!(buf[2], 4);
3459 assert_eq!(&buf[3..7], &asn.to_be_bytes()[..]);
3460 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3461 assert_eq!(decoded.len(), 1);
3462 assert_eq!(decoded[0], PathAttribute::OnlyToCustomer(asn));
3463 }
3464 }
3465
3466 #[test]
3467 fn only_to_customer_type_code_and_flags() {
3468 let attr = PathAttribute::OnlyToCustomer(65001);
3469 assert_eq!(attr.type_code(), 35);
3470 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3471 }
3472
3473 #[test]
3474 fn only_to_customer_malformed_length_stored_as_unknown() {
3475 for bad_value in [
3480 &[0xAA, 0xBB, 0xCC][..], &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE][..], &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11][..], ] {
3484 let mut buf = vec![
3485 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3486 attr_type::ONLY_TO_CUSTOMER,
3487 u8::try_from(bad_value.len()).unwrap(),
3488 ];
3489 buf.extend_from_slice(bad_value);
3490 let decoded = decode_path_attributes(&buf, true, &[])
3491 .expect("malformed-length OTC must NOT be a fatal DecodeError");
3492 assert_eq!(decoded.len(), 1);
3493 match &decoded[0] {
3494 PathAttribute::Unknown(raw) => {
3495 assert_eq!(raw.type_code, attr_type::ONLY_TO_CUSTOMER);
3496 assert_eq!(raw.data.as_ref(), bad_value);
3497 assert_eq!(raw.flags, attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3498 }
3499 other => panic!(
3500 "len {}: expected Unknown(type_code=35), got {other:?}",
3501 bad_value.len()
3502 ),
3503 }
3504 }
3505 }
3506
3507 #[test]
3508 fn only_to_customer_bad_flags_returns_attribute_flags_error() {
3509 for bad_flags in [
3513 0x00u8, attr_flags::TRANSITIVE, attr_flags::OPTIONAL, ] {
3517 let buf = [
3518 bad_flags,
3519 attr_type::ONLY_TO_CUSTOMER,
3520 4, 0x00,
3522 0x00,
3523 0xFD,
3524 0xE9, ];
3526 let err = decode_path_attributes(&buf, true, &[]).expect_err(
3527 "bad-flags OTC must return ATTRIBUTE_FLAGS_ERROR, not Ok or other variant",
3528 );
3529 match err {
3530 DecodeError::UpdateAttributeError { subcode, .. } => {
3531 assert_eq!(
3532 subcode,
3533 update_subcode::ATTRIBUTE_FLAGS_ERROR,
3534 "bad flags {bad_flags:#04x}: expected subcode 4 ATTRIBUTE_FLAGS_ERROR"
3535 );
3536 }
3537 other => panic!("expected UpdateAttributeError, got {other:?}"),
3538 }
3539 }
3540 }
3541
3542 #[test]
3543 fn only_to_customer_partial_bit_preserved_via_unknown() {
3544 let buf = [
3554 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL, attr_type::ONLY_TO_CUSTOMER,
3556 4,
3557 0x00,
3558 0x00,
3559 0xFD,
3560 0xE8, ];
3562 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3563 assert_eq!(decoded.len(), 1);
3564 match &decoded[0] {
3565 PathAttribute::Unknown(raw) => {
3566 assert_eq!(raw.type_code, attr_type::ONLY_TO_CUSTOMER);
3567 assert_eq!(
3568 raw.flags,
3569 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL,
3570 "Partial bit must survive decode for round-trip-faithful re-emission"
3571 );
3572 assert_eq!(raw.data.as_ref(), &[0x00, 0x00, 0xFD, 0xE8][..]);
3573 }
3574 other => panic!(
3575 "Partial-bearing OTC must decode to Unknown (so encode preserves \
3576 Partial via the existing Unknown-encode path); got {other:?}"
3577 ),
3578 }
3579
3580 let mut reencoded = Vec::new();
3584 encode_path_attributes(&decoded, &mut reencoded, true, false);
3585 assert_eq!(
3586 reencoded[0],
3587 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL,
3588 "re-encode must preserve Partial; lost-Partial violates RFC 4271 §5"
3589 );
3590 assert_eq!(reencoded[1], attr_type::ONLY_TO_CUSTOMER);
3591 assert_eq!(reencoded[2], 4);
3592 assert_eq!(&reencoded[3..7], &[0x00, 0x00, 0xFD, 0xE8][..]);
3593 }
3594
3595 #[test]
3596 fn only_to_customer_locally_constructed_emits_canonical_flags() {
3597 let attrs = vec![PathAttribute::OnlyToCustomer(65000)];
3600 let mut buf = Vec::new();
3601 encode_path_attributes(&attrs, &mut buf, true, false);
3602 assert_eq!(
3603 buf[0],
3604 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3605 "locally-constructed OTC must emit 0xC0, never 0xE0"
3606 );
3607 }
3608}