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)]
213enum MpNlriFamily {
214 Unicast,
215 FlowSpec,
216 Evpn,
217}
218
219fn classify_mp_nlri_family(
220 afi: Afi,
221 safi: Safi,
222 attribute: &'static str,
223) -> Result<MpNlriFamily, DecodeError> {
224 match (afi, safi) {
225 (Afi::Ipv4 | Afi::Ipv6, Safi::Unicast) => Ok(MpNlriFamily::Unicast),
226 (Afi::Ipv4 | Afi::Ipv6, Safi::FlowSpec) => Ok(MpNlriFamily::FlowSpec),
227 (Afi::L2Vpn, Safi::Evpn) => Ok(MpNlriFamily::Evpn),
228 (Afi::Ipv4 | Afi::Ipv6 | Afi::L2Vpn, Safi::Multicast)
229 | (Afi::Ipv4 | Afi::Ipv6, Safi::Evpn)
230 | (Afi::L2Vpn, Safi::Unicast | Safi::FlowSpec) => {
231 Err(unsupported_mp_nlri_family(attribute, afi, safi))
232 }
233 }
234}
235
236fn unsupported_mp_nlri_family(attribute: &'static str, afi: Afi, safi: Safi) -> DecodeError {
237 DecodeError::MalformedField {
238 message_type: "UPDATE",
239 detail: format!(
240 "{attribute} unsupported AFI/SAFI {}/{}; supported families are IPv4/IPv6 unicast, IPv4/IPv6 FlowSpec, and L2VPN EVPN",
241 afi as u16, safi as u8
242 ),
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251pub struct ExtendedCommunity(u64);
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub struct DfElectionExtendedCommunity {
256 pub algorithm_id: u8,
258 pub capabilities: u16,
260 pub preference: Option<u16>,
263}
264
265impl ExtendedCommunity {
266 #[must_use]
268 pub fn new(raw: u64) -> Self {
269 Self(raw)
270 }
271
272 #[must_use]
274 pub fn as_u64(self) -> u64 {
275 self.0
276 }
277
278 #[must_use]
280 pub fn type_byte(self) -> u8 {
281 (self.0 >> 56) as u8
282 }
283
284 #[must_use]
286 pub fn subtype(self) -> u8 {
287 self.0.to_be_bytes()[1]
288 }
289
290 #[must_use]
292 pub fn is_transitive(self) -> bool {
293 self.type_byte() & 0x40 == 0
294 }
295
296 #[must_use]
298 pub fn value_bytes(self) -> [u8; 6] {
299 let b = self.0.to_be_bytes();
300 [b[2], b[3], b[4], b[5], b[6], b[7]]
301 }
302
303 #[must_use]
314 pub fn route_target(self) -> Option<(u32, u32)> {
315 if self.subtype() != 0x02 {
316 return None;
317 }
318 self.decode_two_part()
319 }
320
321 #[must_use]
328 pub fn route_origin(self) -> Option<(u32, u32)> {
329 if self.subtype() != 0x03 {
330 return None;
331 }
332 self.decode_two_part()
333 }
334
335 #[must_use]
353 pub fn as_bgp_encapsulation(self) -> Option<u16> {
354 if self.type_byte() & 0x3F != 0x03 || self.subtype() != 0x0C {
355 return None;
356 }
357 let v = self.value_bytes();
358 Some(u16::from_be_bytes([v[4], v[5]]))
359 }
360
361 #[must_use]
365 pub fn bgp_encapsulation(tunnel_type: u16) -> Self {
366 let tt = tunnel_type.to_be_bytes();
367 let raw = u64::from_be_bytes([0x03, 0x0C, 0, 0, 0, 0, tt[0], tt[1]]);
368 Self(raw)
369 }
370
371 #[must_use]
378 pub fn as_mac_mobility(self) -> Option<(bool, u32)> {
379 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x00 {
380 return None;
381 }
382 let v = self.value_bytes();
383 let sticky = (v[0] & 0x01) != 0;
384 let seq = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
385 Some((sticky, seq))
386 }
387
388 #[must_use]
390 pub fn mac_mobility(sticky: bool, sequence: u32) -> Self {
391 let flags = u8::from(sticky);
392 let s = sequence.to_be_bytes();
393 let raw = u64::from_be_bytes([0x06, 0x00, flags, 0, s[0], s[1], s[2], s[3]]);
394 Self(raw)
395 }
396
397 #[must_use]
403 pub fn as_esi_label(self) -> Option<(bool, u32)> {
404 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x01 {
405 return None;
406 }
407 let v = self.value_bytes();
408 let single_active = (v[0] & 0x01) != 0;
409 let label = (u32::from(v[3]) << 16) | (u32::from(v[4]) << 8) | u32::from(v[5]);
410 Some((single_active, label))
411 }
412
413 #[must_use]
417 pub fn esi_label(single_active: bool, label: u32) -> Self {
418 let flags = u8::from(single_active);
419 let l = label & 0x00FF_FFFF;
420 #[expect(clippy::cast_possible_truncation)]
421 let raw = u64::from_be_bytes([
422 0x06,
423 0x01,
424 flags,
425 0,
426 0,
427 (l >> 16) as u8,
428 (l >> 8) as u8,
429 l as u8,
430 ]);
431 Self(raw)
432 }
433
434 #[must_use]
440 pub fn as_es_import_rt(self) -> Option<[u8; 6]> {
441 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x02 {
442 return None;
443 }
444 Some(self.value_bytes())
445 }
446
447 #[must_use]
449 pub fn es_import_rt(mac: [u8; 6]) -> Self {
450 let raw = u64::from_be_bytes([0x06, 0x02, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
451 Self(raw)
452 }
453
454 #[must_use]
457 pub fn as_df_election(self) -> Option<DfElectionExtendedCommunity> {
458 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x06 {
459 return None;
460 }
461 let v = self.value_bytes();
462 let algorithm_id = v[0] & 0x1f;
463 let capabilities = u16::from_be_bytes([v[1], v[2]]);
464 let preference = match algorithm_id {
465 2 | 3 => Some(u16::from_be_bytes([v[4], v[5]])),
466 _ => None,
467 };
468 Some(DfElectionExtendedCommunity {
469 algorithm_id,
470 capabilities,
471 preference,
472 })
473 }
474
475 #[must_use]
482 pub fn df_election(algorithm_id: u8, capabilities: u16, preference: Option<u16>) -> Self {
483 let alg = algorithm_id & 0x1f;
484 let cap = capabilities.to_be_bytes();
485 let pref = preference.unwrap_or(0).to_be_bytes();
486 let raw = u64::from_be_bytes([0x06, 0x06, alg, cap[0], cap[1], 0, pref[0], pref[1]]);
487 Self(raw)
488 }
489
490 #[must_use]
496 pub fn as_link_bandwidth(self) -> Option<(u16, f32)> {
497 if self.type_byte() != 0x40 || self.subtype() != 0x04 {
498 return None;
499 }
500 let v = self.value_bytes();
501 let asn = u16::from_be_bytes([v[0], v[1]]);
502 let bytes_per_sec = f32::from_be_bytes([v[2], v[3], v[4], v[5]]);
503 Some((asn, bytes_per_sec))
504 }
505
506 #[must_use]
511 pub fn link_bandwidth(asn: u16, bytes_per_sec: f32) -> Self {
512 let a = asn.to_be_bytes();
513 let bw = bytes_per_sec.to_be_bytes();
514 let raw = u64::from_be_bytes([0x40, 0x04, a[0], a[1], bw[0], bw[1], bw[2], bw[3]]);
515 Self(raw)
516 }
517
518 #[must_use]
523 pub fn as_router_mac(self) -> Option<[u8; 6]> {
524 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x03 {
525 return None;
526 }
527 Some(self.value_bytes())
528 }
529
530 #[must_use]
532 pub fn router_mac(mac: [u8; 6]) -> Self {
533 let raw = u64::from_be_bytes([0x06, 0x03, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
534 Self(raw)
535 }
536
537 #[must_use]
544 pub fn as_default_gateway(self) -> bool {
545 self.type_byte() & 0x3F == 0x03 && self.subtype() == 0x0D && self.value_bytes() == [0u8; 6]
546 }
547
548 #[must_use]
550 pub fn default_gateway() -> Self {
551 let raw = u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0]);
552 Self(raw)
553 }
554
555 fn decode_two_part(self) -> Option<(u32, u32)> {
561 let v = self.value_bytes();
562 let t = self.type_byte() & 0x3F; match t {
564 0x00 => {
566 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
567 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
568 Some((global, local))
569 }
570 0x01 | 0x02 => {
572 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
573 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
574 Some((global, local))
575 }
576 _ => None,
577 }
578 }
579}
580
581impl fmt::Display for ExtendedCommunity {
582 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
584 if let Some((g, l)) = self.route_target() {
585 if is_ipv4 {
586 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
587 } else {
588 write!(f, "RT:{g}:{l}")
589 }
590 } else if let Some((g, l)) = self.route_origin() {
591 if is_ipv4 {
592 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
593 } else {
594 write!(f, "RO:{g}:{l}")
595 }
596 } else {
597 write!(f, "0x{:016x}", self.0)
598 }
599 }
600}
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
606pub struct LargeCommunity {
607 pub global_admin: u32,
609 pub local_data1: u32,
611 pub local_data2: u32,
613}
614
615impl LargeCommunity {
616 #[must_use]
618 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
619 Self {
620 global_admin,
621 local_data1,
622 local_data2,
623 }
624 }
625}
626
627impl fmt::Display for LargeCommunity {
628 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629 write!(
630 f,
631 "{}:{}:{}",
632 self.global_admin, self.local_data1, self.local_data2
633 )
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq, Hash)]
642pub enum PathAttribute {
643 Origin(Origin),
645 AsPath(AsPath),
647 NextHop(Ipv4Addr),
649 LocalPref(u32),
651 Med(u32),
653 Communities(Vec<u32>),
655 ExtendedCommunities(Vec<ExtendedCommunity>),
657 LargeCommunities(Vec<LargeCommunity>),
659 OriginatorId(Ipv4Addr),
661 ClusterList(Vec<Ipv4Addr>),
663 MpReachNlri(MpReachNlri),
665 MpUnreachNlri(MpUnreachNlri),
667 PmsiTunnel(crate::pmsi::PmsiTunnel),
670 OnlyToCustomer(u32),
673 Unknown(RawAttribute),
675}
676
677impl PathAttribute {
678 #[must_use]
680 pub fn type_code(&self) -> u8 {
681 match self {
682 Self::Origin(_) => attr_type::ORIGIN,
683 Self::AsPath(_) => attr_type::AS_PATH,
684 Self::NextHop(_) => attr_type::NEXT_HOP,
685 Self::LocalPref(_) => attr_type::LOCAL_PREF,
686 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
687 Self::Communities(_) => attr_type::COMMUNITIES,
688 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
689 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
690 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
691 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
692 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
693 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
694 Self::PmsiTunnel(_) => attr_type::PMSI_TUNNEL,
695 Self::OnlyToCustomer(_) => attr_type::ONLY_TO_CUSTOMER,
696 Self::Unknown(raw) => raw.type_code,
697 }
698 }
699
700 #[must_use]
702 pub fn flags(&self) -> u8 {
703 match self {
704 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
705 attr_flags::TRANSITIVE
706 }
707 Self::Med(_)
708 | Self::OriginatorId(_)
709 | Self::ClusterList(_)
710 | Self::MpReachNlri(_)
711 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
712 Self::Communities(_)
713 | Self::ExtendedCommunities(_)
714 | Self::LargeCommunities(_)
715 | Self::PmsiTunnel(_)
716 | Self::OnlyToCustomer(_) => attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
717 Self::Unknown(raw) => raw.flags,
718 }
719 }
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, Hash)]
727pub struct RawAttribute {
728 pub flags: u8,
730 pub type_code: u8,
732 pub data: Bytes,
734}
735
736pub fn decode_path_attributes(
747 mut buf: &[u8],
748 four_octet_as: bool,
749 add_path_families: &[(Afi, Safi)],
750) -> Result<Vec<PathAttribute>, DecodeError> {
751 let mut attrs = Vec::new();
752
753 while !buf.is_empty() {
754 if buf.len() < 2 {
756 return Err(DecodeError::MalformedField {
757 message_type: "UPDATE",
758 detail: "truncated attribute header".to_string(),
759 });
760 }
761
762 let flags = buf[0];
763 let type_code = buf[1];
764 buf = &buf[2..];
765
766 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
767 let value_len = if extended {
768 if buf.len() < 2 {
769 return Err(DecodeError::MalformedField {
770 message_type: "UPDATE",
771 detail: "truncated extended-length attribute".to_string(),
772 });
773 }
774 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
775 buf = &buf[2..];
776 len
777 } else {
778 if buf.is_empty() {
779 return Err(DecodeError::MalformedField {
780 message_type: "UPDATE",
781 detail: "truncated attribute length".to_string(),
782 });
783 }
784 let len = buf[0] as usize;
785 buf = &buf[1..];
786 len
787 };
788
789 if buf.len() < value_len {
790 return Err(DecodeError::MalformedField {
791 message_type: "UPDATE",
792 detail: format!(
793 "attribute type {type_code} value truncated: need {value_len}, have {}",
794 buf.len()
795 ),
796 });
797 }
798
799 let value = &buf[..value_len];
800 buf = &buf[value_len..];
801
802 let attr =
803 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
804 attrs.push(attr);
805 }
806
807 Ok(attrs)
808}
809
810#[expect(clippy::too_many_lines)]
812fn decode_attribute_value(
813 flags: u8,
814 type_code: u8,
815 value: &[u8],
816 four_octet_as: bool,
817 add_path_families: &[(Afi, Safi)],
818) -> Result<PathAttribute, DecodeError> {
819 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
821 if let Some(expected) = expected_flags(type_code)
822 && (flags & flags_mask) != expected
823 {
824 return Err(DecodeError::UpdateAttributeError {
825 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
826 data: attr_error_data(flags, type_code, value),
827 detail: format!(
828 "type {} flags {:#04x} (expected {:#04x})",
829 type_code,
830 flags & flags_mask,
831 expected
832 ),
833 });
834 }
835
836 match type_code {
837 attr_type::ORIGIN => {
838 if value.len() != 1 {
839 return Err(DecodeError::UpdateAttributeError {
840 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
841 data: attr_error_data(flags, type_code, value),
842 detail: format!("ORIGIN length {} (expected 1)", value.len()),
843 });
844 }
845 match Origin::from_u8(value[0]) {
846 Some(origin) => Ok(PathAttribute::Origin(origin)),
847 None => Err(DecodeError::UpdateAttributeError {
848 subcode: update_subcode::INVALID_ORIGIN,
849 data: attr_error_data(flags, type_code, value),
850 detail: format!("invalid ORIGIN value {}", value[0]),
851 }),
852 }
853 }
854
855 attr_type::AS_PATH => {
856 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
857 DecodeError::UpdateAttributeError {
858 subcode: update_subcode::MALFORMED_AS_PATH,
859 data: attr_error_data(flags, type_code, value),
860 detail: e.to_string(),
861 }
862 })?;
863 Ok(PathAttribute::AsPath(AsPath { segments }))
864 }
865
866 attr_type::NEXT_HOP => {
867 if value.len() != 4 {
868 return Err(DecodeError::UpdateAttributeError {
869 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
870 data: attr_error_data(flags, type_code, value),
871 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
872 });
873 }
874 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
875 Ok(PathAttribute::NextHop(addr))
876 }
877
878 attr_type::MULTI_EXIT_DISC => {
879 if value.len() != 4 {
880 return Err(DecodeError::UpdateAttributeError {
881 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
882 data: attr_error_data(flags, type_code, value),
883 detail: format!("MED length {} (expected 4)", value.len()),
884 });
885 }
886 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
887 Ok(PathAttribute::Med(med))
888 }
889
890 attr_type::LOCAL_PREF => {
891 if value.len() != 4 {
892 return Err(DecodeError::UpdateAttributeError {
893 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
894 data: attr_error_data(flags, type_code, value),
895 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
896 });
897 }
898 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
899 Ok(PathAttribute::LocalPref(lp))
900 }
901
902 attr_type::COMMUNITIES => {
903 if !value.len().is_multiple_of(4) {
904 return Err(DecodeError::UpdateAttributeError {
905 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
906 data: attr_error_data(flags, type_code, value),
907 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
908 });
909 }
910 let communities = value
911 .chunks_exact(4)
912 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
913 .collect();
914 Ok(PathAttribute::Communities(communities))
915 }
916
917 attr_type::EXTENDED_COMMUNITIES => {
918 if !value.len().is_multiple_of(8) {
919 return Err(DecodeError::UpdateAttributeError {
920 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
921 data: attr_error_data(flags, type_code, value),
922 detail: format!(
923 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
924 value.len()
925 ),
926 });
927 }
928 let communities = value
929 .chunks_exact(8)
930 .map(|c| {
931 ExtendedCommunity::new(u64::from_be_bytes([
932 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
933 ]))
934 })
935 .collect();
936 Ok(PathAttribute::ExtendedCommunities(communities))
937 }
938
939 attr_type::ORIGINATOR_ID => {
940 if value.len() != 4 {
941 return Err(DecodeError::UpdateAttributeError {
942 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
943 data: attr_error_data(flags, type_code, value),
944 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
945 });
946 }
947 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
948 Ok(PathAttribute::OriginatorId(addr))
949 }
950
951 attr_type::CLUSTER_LIST => {
952 if !value.len().is_multiple_of(4) {
953 return Err(DecodeError::UpdateAttributeError {
954 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
955 data: attr_error_data(flags, type_code, value),
956 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
957 });
958 }
959 let ids = value
960 .chunks_exact(4)
961 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
962 .collect();
963 Ok(PathAttribute::ClusterList(ids))
964 }
965
966 attr_type::LARGE_COMMUNITIES => {
967 if value.is_empty() || !value.len().is_multiple_of(12) {
968 return Err(DecodeError::UpdateAttributeError {
969 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
970 data: attr_error_data(flags, type_code, value),
971 detail: format!(
972 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
973 value.len()
974 ),
975 });
976 }
977 let communities = value
978 .chunks_exact(12)
979 .map(|c| {
980 LargeCommunity::new(
981 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
982 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
983 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
984 )
985 })
986 .collect();
987 Ok(PathAttribute::LargeCommunities(communities))
988 }
989
990 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
991 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
992
993 attr_type::PMSI_TUNNEL => {
994 let pmsi = crate::pmsi::PmsiTunnel::decode(value)?;
995 Ok(PathAttribute::PmsiTunnel(pmsi))
996 }
997
998 attr_type::ONLY_TO_CUSTOMER => {
999 if value.len() != 4 || (flags & attr_flags::PARTIAL) != 0 {
1025 return Ok(PathAttribute::Unknown(RawAttribute {
1026 flags,
1027 type_code,
1028 data: Bytes::copy_from_slice(value),
1029 }));
1030 }
1031 let asn = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
1032 Ok(PathAttribute::OnlyToCustomer(asn))
1033 }
1034
1035 _ => Ok(PathAttribute::Unknown(RawAttribute {
1037 flags,
1038 type_code,
1039 data: Bytes::copy_from_slice(value),
1040 })),
1041 }
1042}
1043
1044#[expect(clippy::too_many_lines)]
1049fn decode_mp_reach_nlri(
1050 value: &[u8],
1051 add_path_families: &[(Afi, Safi)],
1052) -> Result<PathAttribute, DecodeError> {
1053 if value.len() < 5 {
1054 return Err(DecodeError::MalformedField {
1055 message_type: "UPDATE",
1056 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
1057 });
1058 }
1059
1060 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1061 let safi_raw = value[2];
1062 let nh_len = value[3] as usize;
1063
1064 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1065 message_type: "UPDATE",
1066 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
1067 })?;
1068 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1069 message_type: "UPDATE",
1070 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
1071 })?;
1072 let family = classify_mp_nlri_family(afi, safi, "MP_REACH_NLRI")?;
1073
1074 if value.len() < 4 + nh_len + 1 {
1076 return Err(DecodeError::MalformedField {
1077 message_type: "UPDATE",
1078 detail: format!(
1079 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
1080 value.len()
1081 ),
1082 });
1083 }
1084
1085 let nh_bytes = &value[4..4 + nh_len];
1086 let mut link_local_next_hop: Option<Ipv6Addr> = None;
1088 let next_hop = match family {
1089 MpNlriFamily::FlowSpec => {
1090 if nh_len != 0 {
1091 return Err(DecodeError::MalformedField {
1092 message_type: "UPDATE",
1093 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
1094 });
1095 }
1096 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
1097 }
1098 MpNlriFamily::Unicast | MpNlriFamily::Evpn => match afi {
1099 Afi::Ipv4 => match nh_len {
1100 4 => IpAddr::V4(Ipv4Addr::new(
1101 nh_bytes[0],
1102 nh_bytes[1],
1103 nh_bytes[2],
1104 nh_bytes[3],
1105 )),
1106 16 | 32 => {
1107 let mut octets = [0u8; 16];
1108 octets.copy_from_slice(&nh_bytes[..16]);
1109 if nh_len == 32 {
1110 let mut ll = [0u8; 16];
1111 ll.copy_from_slice(&nh_bytes[16..32]);
1112 link_local_next_hop = Some(Ipv6Addr::from(ll));
1113 }
1114 IpAddr::V6(Ipv6Addr::from(octets))
1115 }
1116 _ => {
1117 return Err(DecodeError::MalformedField {
1118 message_type: "UPDATE",
1119 detail: format!(
1120 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
1121 ),
1122 });
1123 }
1124 },
1125 Afi::Ipv6 => {
1126 if nh_len != 16 && nh_len != 32 {
1127 return Err(DecodeError::MalformedField {
1128 message_type: "UPDATE",
1129 detail: format!(
1130 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
1131 ),
1132 });
1133 }
1134 let mut octets = [0u8; 16];
1135 octets.copy_from_slice(&nh_bytes[..16]);
1136 if nh_len == 32 {
1137 let mut ll = [0u8; 16];
1138 ll.copy_from_slice(&nh_bytes[16..32]);
1139 link_local_next_hop = Some(Ipv6Addr::from(ll));
1140 }
1141 IpAddr::V6(Ipv6Addr::from(octets))
1142 }
1143 Afi::L2Vpn => match nh_len {
1144 4 => IpAddr::V4(Ipv4Addr::new(
1145 nh_bytes[0],
1146 nh_bytes[1],
1147 nh_bytes[2],
1148 nh_bytes[3],
1149 )),
1150 16 => {
1151 let mut octets = [0u8; 16];
1152 octets.copy_from_slice(&nh_bytes[..16]);
1153 IpAddr::V6(Ipv6Addr::from(octets))
1154 }
1155 _ => {
1156 return Err(DecodeError::MalformedField {
1157 message_type: "UPDATE",
1158 detail: format!(
1159 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
1160 ),
1161 });
1162 }
1163 },
1164 },
1165 };
1166
1167 let nlri_start = 4 + nh_len + 1;
1169 let nlri_bytes = &value[nlri_start..];
1170
1171 if family == MpNlriFamily::FlowSpec {
1173 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1174 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1175 afi,
1176 safi,
1177 next_hop,
1178 link_local_next_hop,
1179 announced: vec![],
1180 flowspec_announced: flowspec_rules,
1181 evpn_announced: vec![],
1182 }));
1183 }
1184
1185 if family == MpNlriFamily::Evpn {
1187 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1188 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1189 afi,
1190 safi,
1191 next_hop,
1192 link_local_next_hop,
1193 announced: vec![],
1194 flowspec_announced: vec![],
1195 evpn_announced: routes,
1196 }));
1197 }
1198
1199 let add_path = add_path_families.contains(&(afi, safi));
1200 let announced = match (afi, add_path) {
1201 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1202 .into_iter()
1203 .map(|p| NlriEntry {
1204 path_id: 0,
1205 prefix: Prefix::V4(p),
1206 })
1207 .collect(),
1208 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1209 .into_iter()
1210 .map(|e| NlriEntry {
1211 path_id: e.path_id,
1212 prefix: Prefix::V4(e.prefix),
1213 })
1214 .collect(),
1215 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1216 .into_iter()
1217 .map(|p| NlriEntry {
1218 path_id: 0,
1219 prefix: Prefix::V6(p),
1220 })
1221 .collect(),
1222 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1223 (Afi::L2Vpn, _) => return Err(unsupported_mp_nlri_family("MP_REACH_NLRI", afi, safi)),
1224 };
1225
1226 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1227 afi,
1228 safi,
1229 next_hop,
1230 link_local_next_hop,
1231 announced,
1232 flowspec_announced: vec![],
1233 evpn_announced: vec![],
1234 }))
1235}
1236
1237fn decode_mp_unreach_nlri(
1242 value: &[u8],
1243 add_path_families: &[(Afi, Safi)],
1244) -> Result<PathAttribute, DecodeError> {
1245 if value.len() < 3 {
1246 return Err(DecodeError::MalformedField {
1247 message_type: "UPDATE",
1248 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1249 });
1250 }
1251
1252 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1253 let safi_raw = value[2];
1254
1255 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1256 message_type: "UPDATE",
1257 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1258 })?;
1259 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1260 message_type: "UPDATE",
1261 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1262 })?;
1263 let family = classify_mp_nlri_family(afi, safi, "MP_UNREACH_NLRI")?;
1264
1265 let withdrawn_bytes = &value[3..];
1266
1267 if family == MpNlriFamily::FlowSpec {
1269 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1270 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1271 afi,
1272 safi,
1273 withdrawn: vec![],
1274 flowspec_withdrawn: flowspec_rules,
1275 evpn_withdrawn: vec![],
1276 }));
1277 }
1278
1279 if family == MpNlriFamily::Evpn {
1281 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1282 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1283 afi,
1284 safi,
1285 withdrawn: vec![],
1286 flowspec_withdrawn: vec![],
1287 evpn_withdrawn: routes,
1288 }));
1289 }
1290
1291 let add_path = add_path_families.contains(&(afi, safi));
1292 let withdrawn = match (afi, add_path) {
1293 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1294 .into_iter()
1295 .map(|p| NlriEntry {
1296 path_id: 0,
1297 prefix: Prefix::V4(p),
1298 })
1299 .collect(),
1300 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1301 .into_iter()
1302 .map(|e| NlriEntry {
1303 path_id: e.path_id,
1304 prefix: Prefix::V4(e.prefix),
1305 })
1306 .collect(),
1307 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1308 .into_iter()
1309 .map(|p| NlriEntry {
1310 path_id: 0,
1311 prefix: Prefix::V6(p),
1312 })
1313 .collect(),
1314 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1315 (Afi::L2Vpn, _) => return Err(unsupported_mp_nlri_family("MP_UNREACH_NLRI", afi, safi)),
1316 };
1317
1318 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1319 afi,
1320 safi,
1321 withdrawn,
1322 flowspec_withdrawn: vec![],
1323 evpn_withdrawn: vec![],
1324 }))
1325}
1326
1327fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1329 let as_size: usize = if four_octet_as { 4 } else { 2 };
1330 let mut segments = Vec::new();
1331
1332 while !buf.is_empty() {
1333 if buf.len() < 2 {
1334 return Err(DecodeError::MalformedField {
1335 message_type: "UPDATE",
1336 detail: "truncated AS_PATH segment header".to_string(),
1337 });
1338 }
1339
1340 let seg_type = buf[0];
1341 let seg_count = buf[1] as usize;
1342 buf = &buf[2..];
1343
1344 let needed = seg_count * as_size;
1345 if buf.len() < needed {
1346 return Err(DecodeError::MalformedField {
1347 message_type: "UPDATE",
1348 detail: format!(
1349 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1350 buf.len()
1351 ),
1352 });
1353 }
1354
1355 let mut asns = Vec::with_capacity(seg_count);
1356 for _ in 0..seg_count {
1357 let asn = if four_octet_as {
1358 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1359 buf = &buf[4..];
1360 v
1361 } else {
1362 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1363 buf = &buf[2..];
1364 v
1365 };
1366 asns.push(asn);
1367 }
1368
1369 match seg_type {
1370 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1371 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1372 _ => {
1373 return Err(DecodeError::MalformedField {
1374 message_type: "UPDATE",
1375 detail: format!("unknown AS_PATH segment type {seg_type}"),
1376 });
1377 }
1378 }
1379 }
1380
1381 Ok(segments)
1382}
1383
1384pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1387 let mut buf = Vec::with_capacity(3 + value.len());
1388 if value.len() > 255 {
1389 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1390 buf.push(type_code);
1391 #[expect(clippy::cast_possible_truncation)]
1392 let len = value.len() as u16;
1393 buf.extend_from_slice(&len.to_be_bytes());
1394 } else {
1395 buf.push(flags);
1396 buf.push(type_code);
1397 #[expect(clippy::cast_possible_truncation)]
1398 buf.push(value.len() as u8);
1399 }
1400 buf.extend_from_slice(value);
1401 buf
1402}
1403
1404fn expected_flags(type_code: u8) -> Option<u8> {
1407 match type_code {
1408 attr_type::ORIGIN
1410 | attr_type::AS_PATH
1411 | attr_type::NEXT_HOP
1412 | attr_type::LOCAL_PREF
1413 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1414 attr_type::MULTI_EXIT_DISC
1417 | attr_type::ORIGINATOR_ID
1418 | attr_type::CLUSTER_LIST
1419 | attr_type::MP_REACH_NLRI
1420 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1421 attr_type::AGGREGATOR
1423 | attr_type::COMMUNITIES
1424 | attr_type::EXTENDED_COMMUNITIES
1425 | attr_type::LARGE_COMMUNITIES
1426 | attr_type::PMSI_TUNNEL
1427 | attr_type::ONLY_TO_CUSTOMER => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1428 _ => None,
1429 }
1430}
1431
1432#[expect(
1440 clippy::too_many_lines,
1441 reason = "dispatch arms are inherently O(variants); each new path attribute adds a small block"
1442)]
1443pub fn encode_path_attributes(
1444 attrs: &[PathAttribute],
1445 buf: &mut Vec<u8>,
1446 four_octet_as: bool,
1447 add_path_mp: bool,
1448) {
1449 for attr in attrs {
1450 let mut value = Vec::new();
1451 let flags;
1452 let type_code;
1453
1454 match attr {
1455 PathAttribute::Origin(origin) => {
1456 flags = attr_flags::TRANSITIVE;
1457 type_code = attr_type::ORIGIN;
1458 value.push(*origin as u8);
1459 }
1460 PathAttribute::AsPath(as_path) => {
1461 flags = attr_flags::TRANSITIVE;
1462 type_code = attr_type::AS_PATH;
1463 encode_as_path(as_path, &mut value, four_octet_as);
1464 }
1465 PathAttribute::NextHop(addr) => {
1466 flags = attr_flags::TRANSITIVE;
1467 type_code = attr_type::NEXT_HOP;
1468 value.extend_from_slice(&addr.octets());
1469 }
1470 PathAttribute::Med(med) => {
1471 flags = attr_flags::OPTIONAL;
1472 type_code = attr_type::MULTI_EXIT_DISC;
1473 value.extend_from_slice(&med.to_be_bytes());
1474 }
1475 PathAttribute::LocalPref(lp) => {
1476 flags = attr_flags::TRANSITIVE;
1477 type_code = attr_type::LOCAL_PREF;
1478 value.extend_from_slice(&lp.to_be_bytes());
1479 }
1480 PathAttribute::Communities(communities) => {
1481 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1482 type_code = attr_type::COMMUNITIES;
1483 for &c in communities {
1484 value.extend_from_slice(&c.to_be_bytes());
1485 }
1486 }
1487 PathAttribute::ExtendedCommunities(communities) => {
1488 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1489 type_code = attr_type::EXTENDED_COMMUNITIES;
1490 for &c in communities {
1491 value.extend_from_slice(&c.as_u64().to_be_bytes());
1492 }
1493 }
1494 PathAttribute::LargeCommunities(communities) => {
1495 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1496 type_code = attr_type::LARGE_COMMUNITIES;
1497 for &c in communities {
1498 value.extend_from_slice(&c.global_admin.to_be_bytes());
1499 value.extend_from_slice(&c.local_data1.to_be_bytes());
1500 value.extend_from_slice(&c.local_data2.to_be_bytes());
1501 }
1502 }
1503 PathAttribute::OriginatorId(addr) => {
1504 flags = attr_flags::OPTIONAL;
1505 type_code = attr_type::ORIGINATOR_ID;
1506 value.extend_from_slice(&addr.octets());
1507 }
1508 PathAttribute::ClusterList(ids) => {
1509 flags = attr_flags::OPTIONAL;
1510 type_code = attr_type::CLUSTER_LIST;
1511 for id in ids {
1512 value.extend_from_slice(&id.octets());
1513 }
1514 }
1515 PathAttribute::MpReachNlri(mp) => {
1516 flags = attr_flags::OPTIONAL;
1517 type_code = attr_type::MP_REACH_NLRI;
1518 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1519 }
1520 PathAttribute::MpUnreachNlri(mp) => {
1521 flags = attr_flags::OPTIONAL;
1522 type_code = attr_type::MP_UNREACH_NLRI;
1523 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1524 }
1525 PathAttribute::PmsiTunnel(pmsi) => {
1526 (flags, type_code) = (
1528 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
1529 attr_type::PMSI_TUNNEL,
1530 );
1531 pmsi.encode(&mut value);
1532 }
1533 PathAttribute::OnlyToCustomer(asn) => {
1534 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1539 type_code = attr_type::ONLY_TO_CUSTOMER;
1540 value.extend_from_slice(&asn.to_be_bytes());
1541 }
1542 PathAttribute::Unknown(raw) => {
1543 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1547 flags = if (raw.flags & optional_transitive) == optional_transitive {
1548 raw.flags | attr_flags::PARTIAL
1549 } else {
1550 raw.flags
1551 };
1552 type_code = raw.type_code;
1553 value.extend_from_slice(&raw.data);
1554 }
1555 }
1556
1557 if value.len() > 255 {
1559 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1560 buf.push(type_code);
1561 #[expect(clippy::cast_possible_truncation)]
1562 let len = value.len() as u16;
1563 buf.extend_from_slice(&len.to_be_bytes());
1564 } else {
1565 buf.push(flags);
1566 buf.push(type_code);
1567 #[expect(clippy::cast_possible_truncation)]
1568 buf.push(value.len() as u8);
1569 }
1570 buf.extend_from_slice(&value);
1571 }
1572}
1573
1574fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1579 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1580 buf.push(mp.safi as u8);
1581
1582 if mp.safi == Safi::FlowSpec {
1584 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1587 return;
1588 }
1589
1590 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1592 match mp.next_hop {
1593 IpAddr::V4(addr) => {
1594 buf.push(4);
1595 buf.extend_from_slice(&addr.octets());
1596 }
1597 IpAddr::V6(addr) => {
1598 buf.push(16);
1599 buf.extend_from_slice(&addr.octets());
1600 }
1601 }
1602 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1604 return;
1605 }
1606
1607 match (mp.next_hop, mp.link_local_next_hop) {
1608 (IpAddr::V4(addr), _) => {
1609 buf.push(4); buf.extend_from_slice(&addr.octets());
1611 }
1612 (IpAddr::V6(addr), Some(ll)) => {
1613 debug_assert!(
1625 (ll.segments()[0] & 0xffc0) == 0xfe80,
1626 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1627 );
1628 buf.push(32); buf.extend_from_slice(&addr.octets());
1630 buf.extend_from_slice(&ll.octets());
1631 }
1632 (IpAddr::V6(addr), None) => {
1633 buf.push(16); buf.extend_from_slice(&addr.octets());
1635 }
1636 }
1637
1638 buf.push(0); if add_path {
1641 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1642 } else {
1643 for entry in &mp.announced {
1644 match entry.prefix {
1645 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1646 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1647 }
1648 }
1649 }
1650}
1651
1652fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1656 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1657 buf.push(mp.safi as u8);
1658
1659 if mp.safi == Safi::FlowSpec {
1661 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1662 return;
1663 }
1664
1665 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1667 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1668 return;
1669 }
1670
1671 if add_path {
1672 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1673 } else {
1674 for entry in &mp.withdrawn {
1675 match entry.prefix {
1676 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1677 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1678 }
1679 }
1680 }
1681}
1682
1683fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1685 for segment in &as_path.segments {
1686 let (seg_type, asns) = match segment {
1687 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1688 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1689 };
1690 for chunk in asns.chunks(u8::MAX as usize) {
1691 buf.push(seg_type);
1692 #[expect(clippy::cast_possible_truncation)]
1693 buf.push(chunk.len() as u8);
1694 for &asn in chunk {
1695 if four_octet_as {
1696 buf.extend_from_slice(&asn.to_be_bytes());
1697 } else {
1698 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1701 buf.extend_from_slice(&as2.to_be_bytes());
1702 }
1703 }
1704 }
1705 }
1706}
1707
1708#[cfg(test)]
1709mod tests {
1710 use super::*;
1711
1712 #[test]
1713 fn mp_reach_evpn_attribute_roundtrip() {
1714 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1715
1716 let mp = MpReachNlri {
1717 afi: Afi::L2Vpn,
1718 safi: Safi::Evpn,
1719 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1720 link_local_next_hop: None,
1721 announced: vec![],
1722 flowspec_announced: vec![],
1723 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1724 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1725 ethernet_tag: EthernetTagId(100),
1726 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1727 })],
1728 };
1729 let attr = PathAttribute::MpReachNlri(mp);
1730
1731 let mut buf = Vec::new();
1732 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1733 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1734 assert_eq!(decoded.len(), 1);
1735 assert_eq!(attr, decoded[0]);
1736
1737 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1738 panic!("not MP_REACH after decode");
1739 };
1740 assert_eq!(dec.afi, Afi::L2Vpn);
1741 assert_eq!(dec.safi, Safi::Evpn);
1742 assert_eq!(dec.evpn_announced.len(), 1);
1743 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1744 }
1745
1746 #[test]
1757 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1758 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1759
1760 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1761 let mp = MpReachNlri {
1762 afi: Afi::L2Vpn,
1763 safi: Safi::Evpn,
1764 next_hop: IpAddr::V6(vtep_v6),
1765 link_local_next_hop: None,
1766 announced: vec![],
1767 flowspec_announced: vec![],
1768 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1769 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1770 ethernet_tag: EthernetTagId(100),
1771 originator_ip: IpAddr::V6(vtep_v6),
1772 })],
1773 };
1774 let attr = PathAttribute::MpReachNlri(mp.clone());
1775
1776 let mut buf = Vec::new();
1777 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1778
1779 let extended = (buf[0] & 0x10) != 0;
1790 let value_off = if extended { 4 } else { 3 };
1791 assert_eq!(
1792 buf[value_off + 3],
1793 16,
1794 "EVPN IPv6 NH-Len must be 16, not 32"
1795 );
1796 assert_eq!(
1797 &buf[value_off + 4..value_off + 20],
1798 &vtep_v6.octets(),
1799 "encoded VTEP next-hop bytes must match the input"
1800 );
1801
1802 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1803 assert_eq!(decoded.len(), 1);
1804 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1805
1806 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1807 panic!("not MP_REACH after decode");
1808 };
1809 assert_eq!(dec.afi, Afi::L2Vpn);
1810 assert_eq!(dec.safi, Safi::Evpn);
1811 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1812 assert!(
1813 dec.link_local_next_hop.is_none(),
1814 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1815 );
1816 assert_eq!(dec.evpn_announced.len(), 1);
1817 match &dec.evpn_announced[0] {
1818 EvpnRoute::Imet(imet) => {
1819 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1820 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1821 }
1822 other => panic!("expected IMET, got {other:?}"),
1823 }
1824 }
1825
1826 #[test]
1833 fn mp_reach_evpn_rejects_32byte_next_hop() {
1834 let mut attr = vec![0x80u8, 14, 37];
1840 attr.extend_from_slice(&[
1841 0x00, 0x19, 0x46, 0x20, ]);
1845 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1849 match err {
1850 DecodeError::MalformedField { detail, .. } => {
1851 assert!(
1852 detail.contains("L2VPN next-hop length 32"),
1853 "expected L2VPN NH-Len rejection, got: {detail}"
1854 );
1855 }
1856 other => panic!("expected MalformedField, got: {other:?}"),
1857 }
1858 }
1859
1860 #[test]
1861 fn mp_unreach_evpn_attribute_roundtrip() {
1862 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1863
1864 let mp = MpUnreachNlri {
1865 afi: Afi::L2Vpn,
1866 safi: Safi::Evpn,
1867 withdrawn: vec![],
1868 flowspec_withdrawn: vec![],
1869 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1870 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1871 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1872 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1873 })],
1874 };
1875 let attr = PathAttribute::MpUnreachNlri(mp);
1876 let mut buf = Vec::new();
1877 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1878 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1879 assert_eq!(decoded.len(), 1);
1880 assert_eq!(attr, decoded[0]);
1881 }
1882
1883 #[test]
1886 fn ext_comm_bgp_encapsulation_vxlan() {
1887 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1889 assert_eq!(c.subtype(), 0x0C);
1890 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1891 let b = c.as_u64().to_be_bytes();
1893 assert_eq!(b[2..6], [0, 0, 0, 0]);
1894 assert_eq!(&b[6..8], &[0, 8]);
1895 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1897 }
1898
1899 #[test]
1900 fn ext_comm_mac_mobility_sticky_and_sequence() {
1901 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1902 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1903 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1904 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1905 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1907 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1908 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1909 }
1910
1911 #[test]
1912 fn ext_comm_esi_label_flags_and_label() {
1913 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1914 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1915 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1916 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1917 }
1918
1919 #[test]
1920 fn ext_comm_es_import_rt_mac() {
1921 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1922 let e = ExtendedCommunity::es_import_rt(mac);
1923 assert_eq!(e.as_es_import_rt(), Some(mac));
1924 assert_eq!(e.type_byte(), 0x06);
1925 assert_eq!(e.subtype(), 0x02);
1926 }
1927
1928 #[test]
1929 fn ext_comm_df_election_hrw_roundtrips_reserved_bytes_zero() {
1930 let ec = ExtendedCommunity::df_election(1, 0, None);
1931 assert_eq!(ec.type_byte(), 0x06);
1932 assert_eq!(ec.subtype(), 0x06);
1933 assert_eq!(
1934 ec.as_df_election(),
1935 Some(DfElectionExtendedCommunity {
1936 algorithm_id: 1,
1937 capabilities: 0,
1938 preference: None,
1939 })
1940 );
1941 assert_eq!(ec.as_u64().to_be_bytes(), [0x06, 0x06, 0x01, 0, 0, 0, 0, 0]);
1942 }
1943
1944 #[test]
1945 fn ext_comm_df_election_preference_bytes_decode_for_rfc9785_algorithms() {
1946 let ec = ExtendedCommunity::df_election(3, 0x8000, Some(42));
1947 assert_eq!(
1948 ec.as_df_election(),
1949 Some(DfElectionExtendedCommunity {
1950 algorithm_id: 3,
1951 capabilities: 0x8000,
1952 preference: Some(42),
1953 })
1954 );
1955 }
1956
1957 #[test]
1958 fn ext_comm_router_mac() {
1959 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1960 let e = ExtendedCommunity::router_mac(mac);
1961 assert_eq!(e.as_router_mac(), Some(mac));
1962 }
1963
1964 #[test]
1965 fn ext_comm_link_bandwidth_roundtrips() {
1966 let bw = 1.25e9_f32; let e = ExtendedCommunity::link_bandwidth(65001, bw);
1968 assert_eq!(e.type_byte(), 0x40, "non-transitive two-octet-AS-specific");
1969 assert_eq!(e.subtype(), 0x04, "Link Bandwidth subtype");
1970 let (asn, decoded) = e.as_link_bandwidth().expect("decodes as link bandwidth");
1971 assert_eq!(asn, 65001);
1972 assert_eq!(decoded.to_bits(), bw.to_bits());
1974 }
1975
1976 #[test]
1977 fn ext_comm_link_bandwidth_decodes_known_wire_bytes() {
1978 let one = 1.0_f32.to_be_bytes();
1980 let raw = u64::from_be_bytes([0x40, 0x04, 0xFD, 0xE9, one[0], one[1], one[2], one[3]]);
1981 let (asn, bw) = ExtendedCommunity::new(raw)
1982 .as_link_bandwidth()
1983 .expect("decodes as link bandwidth");
1984 assert_eq!(asn, 65001);
1985 assert_eq!(bw.to_bits(), 1.0_f32.to_bits());
1986 }
1987
1988 #[test]
1989 fn ext_comm_link_bandwidth_rejects_wrong_type_or_subtype() {
1990 let transitive = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x04, 0, 0, 0, 0, 0, 0]));
1992 assert!(transitive.as_link_bandwidth().is_none());
1993 let wrong_sub = ExtendedCommunity::new(u64::from_be_bytes([0x40, 0x02, 0, 0, 0, 0, 0, 0]));
1995 assert!(wrong_sub.as_link_bandwidth().is_none());
1996 }
1997
1998 #[test]
1999 fn ext_comm_default_gateway_flag_only() {
2000 let d = ExtendedCommunity::default_gateway();
2001 assert!(d.as_default_gateway());
2002 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
2004 }
2005
2006 #[test]
2010 fn ext_comm_default_gateway_rejects_nonzero_value() {
2011 let malformed =
2013 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
2014 assert!(
2015 !malformed.as_default_gateway(),
2016 "default-gateway accessor must require all-zero value bytes"
2017 );
2018 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
2020 }
2021
2022 #[test]
2023 fn ext_comm_accessors_return_none_on_unrelated_communities() {
2024 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
2026 assert_eq!(rt.as_mac_mobility(), None);
2027 assert_eq!(rt.as_esi_label(), None);
2028 assert_eq!(rt.as_es_import_rt(), None);
2029 assert_eq!(rt.as_router_mac(), None);
2030 assert!(rt.as_link_bandwidth().is_none());
2031 assert!(!rt.as_default_gateway());
2032 }
2033
2034 #[test]
2035 fn origin_from_u8_roundtrip() {
2036 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
2037 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
2038 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
2039 assert_eq!(Origin::from_u8(3), None);
2040 }
2041
2042 #[test]
2043 fn origin_ordering() {
2044 assert!(Origin::Igp < Origin::Egp);
2045 assert!(Origin::Egp < Origin::Incomplete);
2046 }
2047
2048 #[test]
2049 fn as_path_length_calculation() {
2050 let path = AsPath {
2051 segments: vec![
2052 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
2053 AsPathSegment::AsSet(vec![65004, 65005]),
2054 ],
2055 };
2056 assert_eq!(path.len(), 4);
2058 }
2059
2060 #[test]
2061 fn as_path_empty() {
2062 let path = AsPath { segments: vec![] };
2063 assert!(path.is_empty());
2064 assert_eq!(path.len(), 0);
2065 }
2066
2067 #[test]
2068 fn contains_asn_in_sequence() {
2069 let path = AsPath {
2070 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2071 };
2072 assert!(path.contains_asn(65002));
2073 assert!(!path.contains_asn(65004));
2074 }
2075
2076 #[test]
2077 fn contains_asn_in_set() {
2078 let path = AsPath {
2079 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
2080 };
2081 assert!(path.contains_asn(65005));
2082 assert!(!path.contains_asn(65001));
2083 }
2084
2085 #[test]
2086 fn contains_asn_multiple_segments() {
2087 let path = AsPath {
2088 segments: vec![
2089 AsPathSegment::AsSequence(vec![65001, 65002]),
2090 AsPathSegment::AsSet(vec![65003]),
2091 ],
2092 };
2093 assert!(path.contains_asn(65001));
2094 assert!(path.contains_asn(65003));
2095 assert!(!path.contains_asn(65004));
2096 }
2097
2098 #[test]
2099 fn contains_asn_empty_path() {
2100 let path = AsPath { segments: vec![] };
2101 assert!(!path.contains_asn(65001));
2102 }
2103
2104 #[test]
2105 fn is_private_asn_boundaries() {
2106 assert!(!is_private_asn(64_511));
2108 assert!(is_private_asn(64_512));
2109 assert!(is_private_asn(65_534));
2110 assert!(!is_private_asn(65_535));
2111
2112 assert!(!is_private_asn(4_199_999_999));
2114 assert!(is_private_asn(4_200_000_000));
2115 assert!(is_private_asn(4_294_967_294));
2116 assert!(!is_private_asn(4_294_967_295));
2117 }
2118
2119 #[test]
2120 fn all_private_empty_path_is_false() {
2121 let path = AsPath { segments: vec![] };
2122 assert!(!path.all_private());
2123 }
2124
2125 #[test]
2126 fn all_private_mixed_segments() {
2127 let path = AsPath {
2128 segments: vec![
2129 AsPathSegment::AsSet(vec![64_512, 65_000]),
2130 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
2131 ],
2132 };
2133 assert!(path.all_private());
2134
2135 let non_private = AsPath {
2136 segments: vec![
2137 AsPathSegment::AsSet(vec![64_512, 65_000]),
2138 AsPathSegment::AsSequence(vec![65_535]),
2139 ],
2140 };
2141 assert!(!non_private.all_private());
2142 }
2143
2144 #[test]
2145 fn decode_origin_igp() {
2146 let buf = [0x40, 0x01, 0x01, 0x00];
2148 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2149 assert_eq!(attrs.len(), 1);
2150 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2151 }
2152
2153 #[test]
2154 fn decode_origin_egp() {
2155 let buf = [0x40, 0x01, 0x01, 0x01];
2156 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2157 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
2158 }
2159
2160 #[test]
2161 fn decode_origin_invalid_value() {
2162 let buf = [0x40, 0x01, 0x01, 0x05];
2164 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2165 match &err {
2166 DecodeError::UpdateAttributeError { subcode, .. } => {
2167 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2168 }
2169 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2170 }
2171 }
2172
2173 #[test]
2174 fn decode_next_hop() {
2175 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
2177 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2178 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2179 }
2180
2181 #[test]
2182 fn decode_med() {
2183 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
2185 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2186 assert_eq!(attrs[0], PathAttribute::Med(100));
2187 }
2188
2189 #[test]
2190 fn decode_local_pref() {
2191 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
2193 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2194 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
2195 }
2196
2197 #[test]
2198 fn decode_as_path_4byte() {
2199 let buf = [
2202 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2207 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2208 assert_eq!(
2209 attrs[0],
2210 PathAttribute::AsPath(AsPath {
2211 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2212 })
2213 );
2214 }
2215
2216 #[test]
2217 fn decode_as_path_2byte() {
2218 let buf = [
2221 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2226 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2227 assert_eq!(
2228 attrs[0],
2229 PathAttribute::AsPath(AsPath {
2230 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2231 })
2232 );
2233 }
2234
2235 #[test]
2236 fn decode_unknown_attribute_preserved() {
2237 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2239 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2240 assert_eq!(
2241 attrs[0],
2242 PathAttribute::Unknown(RawAttribute {
2243 flags: 0xC0,
2244 type_code: 99,
2245 data: Bytes::from_static(&[1, 2, 3]),
2246 })
2247 );
2248 }
2249
2250 #[test]
2251 fn decode_atomic_aggregate_as_unknown() {
2252 let buf = [0x40, 0x06, 0x00];
2254 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2255 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2256 }
2257
2258 #[test]
2259 fn decode_extended_length() {
2260 let buf = [
2263 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2268 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2269 assert_eq!(
2270 attrs[0],
2271 PathAttribute::AsPath(AsPath {
2272 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2273 })
2274 );
2275 }
2276
2277 #[test]
2278 fn decode_multiple_attributes() {
2279 let mut buf = Vec::new();
2280 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2282 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2284 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2286
2287 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2288 assert_eq!(attrs.len(), 3);
2289 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2290 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2291 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2292 }
2293
2294 #[test]
2295 fn roundtrip_attributes_4byte() {
2296 let attrs = vec![
2297 PathAttribute::Origin(Origin::Igp),
2298 PathAttribute::AsPath(AsPath {
2299 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2300 }),
2301 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2302 PathAttribute::Med(100),
2303 PathAttribute::LocalPref(200),
2304 ];
2305
2306 let mut buf = Vec::new();
2307 encode_path_attributes(&attrs, &mut buf, true, false);
2308 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2309 assert_eq!(decoded, attrs);
2310 }
2311
2312 #[test]
2313 fn roundtrip_attributes_2byte() {
2314 let attrs = vec![
2315 PathAttribute::Origin(Origin::Egp),
2316 PathAttribute::AsPath(AsPath {
2317 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2318 }),
2319 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2320 ];
2321
2322 let mut buf = Vec::new();
2323 encode_path_attributes(&attrs, &mut buf, false, false);
2324 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2325 assert_eq!(decoded, attrs);
2326 }
2327
2328 #[test]
2329 fn reject_truncated_attribute_header() {
2330 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2332 }
2333
2334 #[test]
2335 fn reject_truncated_attribute_value() {
2336 let buf = [0x40, 0x01, 0x01];
2338 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2339 }
2340
2341 #[test]
2342 fn reject_bad_origin_length() {
2343 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2345 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2346 }
2347
2348 #[test]
2349 fn as_path_with_set_and_sequence() {
2350 let attrs = vec![PathAttribute::AsPath(AsPath {
2352 segments: vec![
2353 AsPathSegment::AsSequence(vec![65001]),
2354 AsPathSegment::AsSet(vec![65002, 65003]),
2355 ],
2356 })];
2357
2358 let mut buf = Vec::new();
2359 encode_path_attributes(&attrs, &mut buf, true, false);
2360 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2361 assert_eq!(decoded, attrs);
2362 }
2363
2364 #[test]
2365 fn decode_communities_single() {
2366 let community: u32 = (65001 << 16) | 0x0064;
2369 let bytes = community.to_be_bytes();
2370 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2371 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2372 assert_eq!(attrs.len(), 1);
2373 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2374 }
2375
2376 #[test]
2377 fn decode_communities_multiple() {
2378 let c1: u32 = (65001 << 16) | 0x0064;
2379 let c2: u32 = (65002 << 16) | 0x00C8;
2380 let b1 = c1.to_be_bytes();
2381 let b2 = c2.to_be_bytes();
2382 let buf = [
2383 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2384 ];
2385 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2386 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2387 }
2388
2389 #[test]
2390 fn decode_communities_empty() {
2391 let buf = [0xC0, 0x08, 0x00];
2393 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2394 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2395 }
2396
2397 #[test]
2398 fn decode_communities_odd_length_rejected() {
2399 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2401 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2402 }
2403
2404 #[test]
2405 fn communities_roundtrip() {
2406 let c1: u32 = (65001 << 16) | 0x0064;
2407 let c2: u32 = (65002 << 16) | 0x00C8;
2408 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2409
2410 let mut buf = Vec::new();
2411 encode_path_attributes(&attrs, &mut buf, true, false);
2412 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2413 assert_eq!(decoded, attrs);
2414 }
2415
2416 #[test]
2417 fn communities_type_code_and_flags() {
2418 let attr = PathAttribute::Communities(vec![]);
2419 assert_eq!(attr.type_code(), 8);
2420 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2421 }
2422
2423 #[test]
2426 fn decode_extended_communities_single() {
2427 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2429 let bytes = ec.as_u64().to_be_bytes();
2430 let buf = [
2431 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2432 bytes[7],
2433 ];
2434 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2435 assert_eq!(attrs.len(), 1);
2436 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2437 }
2438
2439 #[test]
2440 fn decode_extended_communities_multiple() {
2441 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2442 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2443 let b1 = ec1.as_u64().to_be_bytes();
2444 let b2 = ec2.as_u64().to_be_bytes();
2445 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2447 buf.extend_from_slice(&b2);
2448 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2449 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2450 }
2451
2452 #[test]
2453 fn decode_extended_communities_empty() {
2454 let buf = [0xC0, 0x10, 0x00];
2455 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2456 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2457 }
2458
2459 #[test]
2460 fn decode_extended_communities_bad_length() {
2461 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2463 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2464 }
2465
2466 #[test]
2467 fn extended_communities_roundtrip() {
2468 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2469 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2470 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2471
2472 let mut buf = Vec::new();
2473 encode_path_attributes(&attrs, &mut buf, true, false);
2474 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2475 assert_eq!(decoded, attrs);
2476 }
2477
2478 #[test]
2479 fn extended_communities_type_code_and_flags() {
2480 let attr = PathAttribute::ExtendedCommunities(vec![]);
2481 assert_eq!(attr.type_code(), 16);
2482 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2483 }
2484
2485 #[test]
2486 fn extended_community_type_subtype() {
2487 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2489 assert_eq!(ec.type_byte(), 0x00);
2490 assert_eq!(ec.subtype(), 0x02);
2491 assert!(ec.is_transitive());
2492 }
2493
2494 #[test]
2495 fn extended_community_route_target() {
2496 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2498 assert_eq!(ec.route_target(), Some((65001, 100)));
2499 assert_eq!(ec.route_origin(), None);
2500
2501 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2503 assert_eq!(ec4.route_target(), Some((65537, 200)));
2504
2505 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2508 let (g, l) = ec_ipv4.route_target().unwrap();
2509 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2511 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2513 }
2514
2515 #[test]
2516 fn extended_community_is_transitive() {
2517 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2519 assert!(t.is_transitive());
2520
2521 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2523 assert!(!nt.is_transitive());
2524 }
2525
2526 #[test]
2527 fn extended_community_display() {
2528 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2529 assert_eq!(rt.to_string(), "RT:65001:100");
2530
2531 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2532 assert_eq!(ro.to_string(), "RO:65001:100");
2533
2534 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2536 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2537
2538 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2540 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2541
2542 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2544 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2545
2546 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2548 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2549 }
2550
2551 #[test]
2552 fn unknown_attribute_roundtrip() {
2553 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2556 flags: 0xC0,
2557 type_code: 99,
2558 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2559 })];
2560
2561 let mut buf = Vec::new();
2562 encode_path_attributes(&attrs, &mut buf, true, false);
2563 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2564 assert_eq!(
2565 decoded,
2566 vec![PathAttribute::Unknown(RawAttribute {
2567 flags: 0xE0, type_code: 99,
2569 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2570 })]
2571 );
2572 }
2573
2574 #[test]
2575 fn origin_with_optional_flag_rejected() {
2576 let buf = [0xC0, 0x01, 0x01, 0x00];
2578 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2579 match &err {
2580 DecodeError::UpdateAttributeError { subcode, .. } => {
2581 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2582 }
2583 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2584 }
2585 }
2586
2587 #[test]
2588 fn med_with_transitive_flag_rejected() {
2589 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2591 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2592 match &err {
2593 DecodeError::UpdateAttributeError { subcode, .. } => {
2594 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2595 }
2596 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2597 }
2598 }
2599
2600 #[test]
2601 fn communities_without_optional_rejected() {
2602 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2604 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2605 match &err {
2606 DecodeError::UpdateAttributeError { subcode, .. } => {
2607 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2608 }
2609 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2610 }
2611 }
2612
2613 #[test]
2614 fn next_hop_length_error_subcode() {
2615 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2617 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2618 match &err {
2619 DecodeError::UpdateAttributeError { subcode, .. } => {
2620 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2621 }
2622 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2623 }
2624 }
2625
2626 #[test]
2627 fn invalid_origin_value_subcode() {
2628 let buf = [0x40, 0x01, 0x01, 0x05];
2630 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2631 match &err {
2632 DecodeError::UpdateAttributeError { subcode, .. } => {
2633 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2634 }
2635 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2636 }
2637 }
2638
2639 #[test]
2640 fn as_path_bad_segment_subcode() {
2641 let buf = [
2643 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2647 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2648 match &err {
2649 DecodeError::UpdateAttributeError { subcode, .. } => {
2650 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2651 }
2652 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2653 }
2654 }
2655
2656 #[test]
2657 fn encode_unknown_transitive_sets_partial() {
2658 let attr = PathAttribute::Unknown(RawAttribute {
2659 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2661 data: Bytes::from_static(&[1, 2]),
2662 });
2663 let mut buf = Vec::new();
2664 encode_path_attributes(&[attr], &mut buf, true, false);
2665 assert_eq!(
2667 buf[0],
2668 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2669 );
2670 }
2671
2672 #[test]
2673 fn encode_unknown_wellknown_transitive_no_partial() {
2674 let attr = PathAttribute::Unknown(RawAttribute {
2676 flags: attr_flags::TRANSITIVE, type_code: 99,
2678 data: Bytes::from_static(&[1, 2]),
2679 });
2680 let mut buf = Vec::new();
2681 encode_path_attributes(&[attr], &mut buf, true, false);
2682 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2683 }
2684
2685 #[test]
2686 fn encode_unknown_nontransitive_no_partial() {
2687 let attr = PathAttribute::Unknown(RawAttribute {
2688 flags: attr_flags::OPTIONAL, type_code: 99,
2690 data: Bytes::from_static(&[1, 2]),
2691 });
2692 let mut buf = Vec::new();
2693 encode_path_attributes(&[attr], &mut buf, true, false);
2694 assert_eq!(buf[0], attr_flags::OPTIONAL);
2696 }
2697
2698 fn nlri(prefix: Prefix) -> NlriEntry {
2702 NlriEntry { path_id: 0, prefix }
2703 }
2704
2705 #[test]
2706 fn mp_reach_nlri_ipv6_roundtrip() {
2707 use crate::capability::{Afi, Safi};
2708 use crate::nlri::{Ipv6Prefix, Prefix};
2709
2710 let mp = MpReachNlri {
2711 afi: Afi::Ipv6,
2712 safi: Safi::Unicast,
2713 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2714 link_local_next_hop: None,
2715 announced: vec![
2716 nlri(Prefix::V6(Ipv6Prefix::new(
2717 "2001:db8:1::".parse().unwrap(),
2718 48,
2719 ))),
2720 nlri(Prefix::V6(Ipv6Prefix::new(
2721 "2001:db8:2::".parse().unwrap(),
2722 48,
2723 ))),
2724 ],
2725 flowspec_announced: vec![],
2726 evpn_announced: vec![],
2727 };
2728 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2729
2730 let mut buf = Vec::new();
2731 encode_path_attributes(&attrs, &mut buf, true, false);
2732 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2733 assert_eq!(decoded.len(), 1);
2734 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2735 }
2736
2737 #[test]
2738 fn mp_unreach_nlri_ipv6_roundtrip() {
2739 use crate::capability::{Afi, Safi};
2740 use crate::nlri::{Ipv6Prefix, Prefix};
2741
2742 let mp = MpUnreachNlri {
2743 afi: Afi::Ipv6,
2744 safi: Safi::Unicast,
2745 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2746 "2001:db8:1::".parse().unwrap(),
2747 48,
2748 )))],
2749 flowspec_withdrawn: vec![],
2750 evpn_withdrawn: vec![],
2751 };
2752 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2753
2754 let mut buf = Vec::new();
2755 encode_path_attributes(&attrs, &mut buf, true, false);
2756 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2757 assert_eq!(decoded.len(), 1);
2758 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2759 }
2760
2761 #[test]
2762 fn mp_reach_nlri_ipv4_roundtrip() {
2763 use crate::capability::{Afi, Safi};
2764 use crate::nlri::Prefix;
2765
2766 let mp = MpReachNlri {
2767 afi: Afi::Ipv4,
2768 safi: Safi::Unicast,
2769 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2770 link_local_next_hop: None,
2771 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2772 Ipv4Addr::new(10, 1, 0, 0),
2773 16,
2774 )))],
2775 flowspec_announced: vec![],
2776 evpn_announced: vec![],
2777 };
2778 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2779
2780 let mut buf = Vec::new();
2781 encode_path_attributes(&attrs, &mut buf, true, false);
2782 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2783 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2784 }
2785
2786 #[test]
2787 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2788 use crate::capability::{Afi, Safi};
2789 use crate::nlri::Prefix;
2790
2791 let mp = MpReachNlri {
2792 afi: Afi::Ipv4,
2793 safi: Safi::Unicast,
2794 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2795 link_local_next_hop: None,
2796 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2797 Ipv4Addr::new(10, 1, 0, 0),
2798 16,
2799 )))],
2800 flowspec_announced: vec![],
2801 evpn_announced: vec![],
2802 };
2803 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2804
2805 let mut buf = Vec::new();
2806 encode_path_attributes(&attrs, &mut buf, true, false);
2807 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2808 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2809 }
2810
2811 #[test]
2812 fn mp_reach_nlri_type_code_and_flags() {
2813 use crate::capability::{Afi, Safi};
2814
2815 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2816 afi: Afi::Ipv6,
2817 safi: Safi::Unicast,
2818 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2819 link_local_next_hop: None,
2820 announced: vec![],
2821 flowspec_announced: vec![],
2822 evpn_announced: vec![],
2823 });
2824 assert_eq!(attr.type_code(), 14);
2825 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2827 }
2828
2829 #[test]
2830 fn mp_unreach_nlri_type_code_and_flags() {
2831 use crate::capability::{Afi, Safi};
2832
2833 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2834 afi: Afi::Ipv6,
2835 safi: Safi::Unicast,
2836 withdrawn: vec![],
2837 flowspec_withdrawn: vec![],
2838 evpn_withdrawn: vec![],
2839 });
2840 assert_eq!(attr.type_code(), 15);
2841 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2842 }
2843
2844 #[test]
2845 fn mp_reach_nlri_empty_nlri() {
2846 use crate::capability::{Afi, Safi};
2847
2848 let mp = MpReachNlri {
2849 afi: Afi::Ipv6,
2850 safi: Safi::Unicast,
2851 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2852 link_local_next_hop: None,
2853 announced: vec![],
2854 flowspec_announced: vec![],
2855 evpn_announced: vec![],
2856 };
2857 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2858
2859 let mut buf = Vec::new();
2860 encode_path_attributes(&attrs, &mut buf, true, false);
2861 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2862 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2863 }
2864
2865 #[test]
2866 fn mp_reach_nlri_bad_flags_rejected() {
2867 let mut value = Vec::new();
2871 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();
2878 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2881 buf.push(value.len() as u8);
2882 buf.extend_from_slice(&value);
2883
2884 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2885 assert!(matches!(
2886 err,
2887 DecodeError::UpdateAttributeError {
2888 subcode: 4, ..
2890 }
2891 ));
2892 }
2893
2894 #[test]
2897 #[expect(clippy::cast_possible_truncation)]
2898 fn mp_reach_nlri_ipv4_addpath_decode() {
2899 use crate::capability::{Afi, Safi};
2900 use crate::nlri::Prefix;
2901
2902 let mut value = Vec::new();
2905 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());
2912 value.push(16);
2913 value.extend_from_slice(&[10, 1]);
2914
2915 let mut buf = Vec::new();
2916 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2919 buf.extend_from_slice(&value);
2920
2921 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2923 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2924 panic!("expected MpReachNlri");
2925 };
2926 assert_eq!(mp.announced.len(), 1);
2927 assert_eq!(mp.announced[0].path_id, 42);
2928 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2929
2930 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2933 }
2934
2935 #[test]
2936 #[expect(clippy::cast_possible_truncation)]
2937 fn mp_reach_nlri_ipv6_addpath_decode() {
2938 use crate::capability::{Afi, Safi};
2939 use crate::nlri::{Ipv6Prefix, Prefix};
2940
2941 let mut value = Vec::new();
2943 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());
2947 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2950 value.push(48);
2951 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2952
2953 let mut buf = Vec::new();
2954 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2957 buf.extend_from_slice(&value);
2958
2959 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2960 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2961 panic!("expected MpReachNlri");
2962 };
2963 assert_eq!(mp.announced.len(), 1);
2964 assert_eq!(mp.announced[0].path_id, 99);
2965 assert_eq!(
2966 mp.announced[0].prefix,
2967 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2968 );
2969 }
2970
2971 #[test]
2972 #[expect(clippy::cast_possible_truncation)]
2973 fn mp_unreach_nlri_ipv6_addpath_decode() {
2974 use crate::capability::{Afi, Safi};
2975 use crate::nlri::{Ipv6Prefix, Prefix};
2976
2977 let mut value = Vec::new();
2979 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2983 value.push(48);
2984 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2985
2986 let mut buf = Vec::new();
2987 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2990 buf.extend_from_slice(&value);
2991
2992 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2993 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2994 panic!("expected MpUnreachNlri");
2995 };
2996 assert_eq!(mp.withdrawn.len(), 1);
2997 assert_eq!(mp.withdrawn[0].path_id, 7);
2998 assert_eq!(
2999 mp.withdrawn[0].prefix,
3000 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
3001 );
3002 }
3003
3004 #[test]
3005 fn mp_reach_addpath_only_applies_to_matching_family() {
3006 use crate::capability::{Afi, Safi};
3007 use crate::nlri::{Ipv6Prefix, Prefix};
3008
3009 let mp = MpReachNlri {
3011 afi: Afi::Ipv6,
3012 safi: Safi::Unicast,
3013 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
3014 link_local_next_hop: None,
3015 announced: vec![NlriEntry {
3016 path_id: 0,
3017 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
3018 }],
3019 flowspec_announced: vec![],
3020 evpn_announced: vec![],
3021 };
3022 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
3023
3024 let mut buf = Vec::new();
3025 encode_path_attributes(&attrs, &mut buf, true, false);
3026
3027 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
3029 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
3030 }
3031
3032 #[test]
3035 fn decode_originator_id() {
3036 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
3038 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3039 assert_eq!(
3040 attrs[0],
3041 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
3042 );
3043 }
3044
3045 #[test]
3050 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
3051 use crate::capability::{Afi, Safi};
3052 use crate::nlri::{Ipv6Prefix, Prefix};
3053 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
3054 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
3055 let mp = MpReachNlri {
3056 afi: Afi::Ipv6,
3057 safi: Safi::Unicast,
3058 next_hop: IpAddr::V6(global),
3059 link_local_next_hop: Some(link_local),
3060 announced: vec![NlriEntry {
3061 path_id: 0,
3062 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
3063 }],
3064 flowspec_announced: vec![],
3065 evpn_announced: vec![],
3066 };
3067 let attr = PathAttribute::MpReachNlri(mp.clone());
3068 let mut buf = Vec::new();
3069 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3070
3071 let extended = (buf[0] & 0x10) != 0;
3075 let value_off = if extended { 4 } else { 3 };
3076 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
3078 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
3079 assert_eq!(
3080 &buf[value_off + 20..value_off + 36],
3081 &link_local.octets(),
3082 "encoded link-local bytes must match the input"
3083 );
3084
3085 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3086 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
3087 panic!("expected MpReachNlri");
3088 };
3089 assert_eq!(dec.next_hop, IpAddr::V6(global));
3090 assert_eq!(dec.link_local_next_hop, Some(link_local));
3091 }
3092
3093 #[test]
3101 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
3102 let value: &[u8] = &[
3105 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
3112 ];
3113 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
3116 attr.extend_from_slice(value);
3117 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
3118 match err {
3119 DecodeError::MalformedField { detail, .. } => {
3120 assert!(
3121 detail.contains("FlowSpec next-hop length"),
3122 "expected FlowSpec NH-Len rejection, got: {detail}"
3123 );
3124 }
3125 other => panic!("expected MalformedField, got {other:?}"),
3126 }
3127 }
3128
3129 #[test]
3130 fn originator_id_roundtrip() {
3131 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
3132 let mut buf = Vec::new();
3133 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3134 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3135 assert_eq!(decoded, vec![attr]);
3136 }
3137
3138 #[test]
3139 fn originator_id_wrong_length() {
3140 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
3142 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3143 assert!(matches!(
3144 err,
3145 DecodeError::UpdateAttributeError {
3146 subcode: 5, ..
3148 }
3149 ));
3150 }
3151
3152 #[test]
3153 fn originator_id_wrong_flags() {
3154 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
3156 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3157 assert!(matches!(
3158 err,
3159 DecodeError::UpdateAttributeError {
3160 subcode: 4, ..
3162 }
3163 ));
3164 }
3165
3166 #[test]
3169 fn decode_cluster_list() {
3170 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
3172 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3173 assert_eq!(
3174 attrs[0],
3175 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
3176 );
3177 }
3178
3179 #[test]
3180 fn cluster_list_roundtrip() {
3181 let attr = PathAttribute::ClusterList(vec![
3182 Ipv4Addr::new(10, 0, 0, 1),
3183 Ipv4Addr::new(10, 0, 0, 2),
3184 ]);
3185 let mut buf = Vec::new();
3186 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3187 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3188 assert_eq!(decoded, vec![attr]);
3189 }
3190
3191 #[test]
3192 fn cluster_list_wrong_length() {
3193 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
3195 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3196 assert!(matches!(
3197 err,
3198 DecodeError::UpdateAttributeError {
3199 subcode: 5, ..
3201 }
3202 ));
3203 }
3204
3205 #[test]
3210 fn large_community_display() {
3211 let lc = LargeCommunity::new(65001, 100, 200);
3212 assert_eq!(lc.to_string(), "65001:100:200");
3213 }
3214
3215 #[test]
3216 fn large_community_type_code_and_flags() {
3217 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3218 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3219 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3220 }
3221
3222 #[test]
3223 fn decode_large_community_single() {
3224 let mut buf = vec![0xC0, 32, 12];
3226 buf.extend_from_slice(&65001u32.to_be_bytes());
3227 buf.extend_from_slice(&100u32.to_be_bytes());
3228 buf.extend_from_slice(&200u32.to_be_bytes());
3229 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3230 assert_eq!(attrs.len(), 1);
3231 assert_eq!(
3232 attrs[0],
3233 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3234 );
3235 }
3236
3237 #[test]
3238 fn decode_large_community_multiple() {
3239 let mut buf = vec![0xC0, 32, 24];
3241 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3242 buf.extend_from_slice(&g.to_be_bytes());
3243 buf.extend_from_slice(&l1.to_be_bytes());
3244 buf.extend_from_slice(&l2.to_be_bytes());
3245 }
3246 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3247 assert_eq!(
3248 attrs[0],
3249 PathAttribute::LargeCommunities(vec![
3250 LargeCommunity::new(65001, 100, 200),
3251 LargeCommunity::new(65002, 300, 400),
3252 ])
3253 );
3254 }
3255
3256 #[test]
3257 fn decode_large_community_bad_length() {
3258 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3260 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3261 assert!(matches!(
3262 err,
3263 DecodeError::UpdateAttributeError {
3264 subcode: 5, ..
3266 }
3267 ));
3268 }
3269
3270 #[test]
3271 fn decode_large_community_empty_rejected() {
3272 let buf = [0xC0, 32, 0];
3274 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3275 assert!(matches!(
3276 err,
3277 DecodeError::UpdateAttributeError {
3278 subcode: 5, ..
3280 }
3281 ));
3282 }
3283
3284 #[test]
3285 fn large_community_roundtrip() {
3286 let lcs = vec![
3287 LargeCommunity::new(65001, 100, 200),
3288 LargeCommunity::new(0, u32::MAX, 42),
3289 ];
3290 let attr = PathAttribute::LargeCommunities(lcs.clone());
3291 let mut buf = Vec::new();
3292 encode_path_attributes(&[attr], &mut buf, true, false);
3293 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3294 assert_eq!(decoded.len(), 1);
3295 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3296 }
3297
3298 #[test]
3299 fn large_community_expected_flags_validated() {
3300 let mut buf = vec![0x40, 32, 12];
3302 buf.extend_from_slice(&1u32.to_be_bytes());
3303 buf.extend_from_slice(&2u32.to_be_bytes());
3304 buf.extend_from_slice(&3u32.to_be_bytes());
3305 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3306 assert!(matches!(
3307 err,
3308 DecodeError::UpdateAttributeError {
3309 subcode: 4, ..
3311 }
3312 ));
3313 }
3314
3315 #[test]
3320 fn aspath_string_sequence() {
3321 let p = AsPath {
3322 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3323 };
3324 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3325 }
3326
3327 #[test]
3328 fn aspath_string_set() {
3329 let p = AsPath {
3330 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3331 };
3332 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3333 }
3334
3335 #[test]
3336 fn aspath_string_mixed() {
3337 let p = AsPath {
3338 segments: vec![
3339 AsPathSegment::AsSequence(vec![65001, 65002]),
3340 AsPathSegment::AsSet(vec![65003, 65004]),
3341 ],
3342 };
3343 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3344 }
3345
3346 #[test]
3347 fn aspath_string_empty() {
3348 let p = AsPath { segments: vec![] };
3349 assert_eq!(p.to_aspath_string(), "");
3350 }
3351
3352 #[test]
3357 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3358 let bytes = vec![
3361 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3367 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3368 match err {
3369 DecodeError::MalformedField { detail, .. } => {
3370 assert!(
3371 detail.contains("unsupported AFI/SAFI 1/70"),
3372 "unexpected detail: {detail}"
3373 );
3374 }
3375 other => panic!("expected MalformedField, got {other:?}"),
3376 }
3377 }
3378
3379 #[test]
3380 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3381 let bytes = vec![
3382 0x00, 0x02, 70, 3, 0, ];
3386 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3387 match err {
3388 DecodeError::MalformedField { detail, .. } => {
3389 assert!(
3390 detail.contains("unsupported AFI/SAFI 2/70"),
3391 "unexpected detail: {detail}"
3392 );
3393 }
3394 other => panic!("expected MalformedField, got {other:?}"),
3395 }
3396 }
3397
3398 #[test]
3399 fn mp_reach_nlri_rejects_multicast_before_prefix_decode() {
3400 let bytes = vec![
3405 0x00, 0x01, 2, 4, 192, 0, 2, 1, 0, 40, 10, 0, 0, 0, 0,
3411 ];
3412 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3413 match err {
3414 DecodeError::MalformedField { detail, .. } => {
3415 assert!(
3416 detail.contains("unsupported AFI/SAFI 1/2"),
3417 "unexpected detail: {detail}"
3418 );
3419 assert!(
3420 !detail.contains("prefix length"),
3421 "unsupported family must reject before prefix decode: {detail}"
3422 );
3423 }
3424 other => panic!("expected MalformedField, got {other:?}"),
3425 }
3426 }
3427
3428 #[test]
3429 fn mp_unreach_nlri_rejects_multicast_before_prefix_decode() {
3430 let bytes = vec![
3433 0x00, 0x02, 2, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3437 ];
3438 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3439 match err {
3440 DecodeError::MalformedField { detail, .. } => {
3441 assert!(
3442 detail.contains("unsupported AFI/SAFI 2/2"),
3443 "unexpected detail: {detail}"
3444 );
3445 assert!(
3446 !detail.contains("prefix length"),
3447 "unsupported family must reject before prefix decode: {detail}"
3448 );
3449 }
3450 other => panic!("expected MalformedField, got {other:?}"),
3451 }
3452 }
3453
3454 #[test]
3455 fn mp_reach_nlri_rejects_l2vpn_flowspec_at_family_gate() {
3456 let bytes = vec![
3457 0x00, 0x19, 133, 0, 0, ];
3462 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3463 match err {
3464 DecodeError::MalformedField { detail, .. } => {
3465 assert!(
3466 detail.contains("unsupported AFI/SAFI 25/133"),
3467 "unexpected detail: {detail}"
3468 );
3469 }
3470 other => panic!("expected MalformedField, got {other:?}"),
3471 }
3472 }
3473
3474 #[test]
3475 fn mp_unreach_nlri_rejects_l2vpn_flowspec_at_family_gate() {
3476 let bytes = vec![
3477 0x00, 0x19, 133, ];
3480 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3481 match err {
3482 DecodeError::MalformedField { detail, .. } => {
3483 assert!(
3484 detail.contains("unsupported AFI/SAFI 25/133"),
3485 "unexpected detail: {detail}"
3486 );
3487 }
3488 other => panic!("expected MalformedField, got {other:?}"),
3489 }
3490 }
3491
3492 #[test]
3493 fn pmsi_tunnel_path_attribute_round_trips_through_dispatch() {
3494 let pmsi =
3499 crate::pmsi::PmsiTunnel::for_evpn_ingress_replication(100, "10.0.0.1".parse().unwrap());
3500 let attrs = vec![
3501 PathAttribute::Origin(Origin::Igp),
3502 PathAttribute::AsPath(AsPath { segments: vec![] }),
3503 PathAttribute::LocalPref(100),
3504 PathAttribute::PmsiTunnel(pmsi.clone()),
3505 ];
3506
3507 let mut buf = Vec::new();
3508 encode_path_attributes(&attrs, &mut buf, true, false);
3509 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3510
3511 assert_eq!(decoded, attrs);
3512
3513 let pmsi_decoded = decoded
3516 .iter()
3517 .find_map(|a| match a {
3518 PathAttribute::PmsiTunnel(p) => Some(p),
3519 _ => None,
3520 })
3521 .expect("PMSI present");
3522 assert_eq!(pmsi_decoded, &pmsi);
3523 assert_eq!(
3524 PathAttribute::PmsiTunnel(pmsi).flags(),
3525 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3526 );
3527 }
3528
3529 #[test]
3530 fn pmsi_tunnel_decode_attribute_with_truncated_value_is_malformed() {
3531 let buf = [
3533 0xC0, 22, 0x04, 0x00, 0x06, 0x00, 0x00,
3537 ];
3538 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3539 assert!(matches!(err, DecodeError::MalformedField { .. }));
3540 }
3541
3542 #[test]
3545 fn only_to_customer_encode_decode_roundtrip() {
3546 for asn in [0u32, 65000, 65536, 4_200_000_000, u32::MAX] {
3547 let attrs = vec![PathAttribute::OnlyToCustomer(asn)];
3548 let mut buf = Vec::new();
3549 encode_path_attributes(&attrs, &mut buf, true, false);
3550 assert_eq!(buf[0], attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3552 assert_eq!(buf[1], attr_type::ONLY_TO_CUSTOMER);
3553 assert_eq!(buf[2], 4);
3554 assert_eq!(&buf[3..7], &asn.to_be_bytes()[..]);
3555 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3556 assert_eq!(decoded.len(), 1);
3557 assert_eq!(decoded[0], PathAttribute::OnlyToCustomer(asn));
3558 }
3559 }
3560
3561 #[test]
3562 fn only_to_customer_type_code_and_flags() {
3563 let attr = PathAttribute::OnlyToCustomer(65001);
3564 assert_eq!(attr.type_code(), 35);
3565 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3566 }
3567
3568 #[test]
3569 fn only_to_customer_malformed_length_stored_as_unknown() {
3570 for bad_value in [
3575 &[0xAA, 0xBB, 0xCC][..], &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE][..], &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11][..], ] {
3579 let mut buf = vec![
3580 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3581 attr_type::ONLY_TO_CUSTOMER,
3582 u8::try_from(bad_value.len()).unwrap(),
3583 ];
3584 buf.extend_from_slice(bad_value);
3585 let decoded = decode_path_attributes(&buf, true, &[])
3586 .expect("malformed-length OTC must NOT be a fatal DecodeError");
3587 assert_eq!(decoded.len(), 1);
3588 match &decoded[0] {
3589 PathAttribute::Unknown(raw) => {
3590 assert_eq!(raw.type_code, attr_type::ONLY_TO_CUSTOMER);
3591 assert_eq!(raw.data.as_ref(), bad_value);
3592 assert_eq!(raw.flags, attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3593 }
3594 other => panic!(
3595 "len {}: expected Unknown(type_code=35), got {other:?}",
3596 bad_value.len()
3597 ),
3598 }
3599 }
3600 }
3601
3602 #[test]
3603 fn only_to_customer_bad_flags_returns_attribute_flags_error() {
3604 for bad_flags in [
3608 0x00u8, attr_flags::TRANSITIVE, attr_flags::OPTIONAL, ] {
3612 let buf = [
3613 bad_flags,
3614 attr_type::ONLY_TO_CUSTOMER,
3615 4, 0x00,
3617 0x00,
3618 0xFD,
3619 0xE9, ];
3621 let err = decode_path_attributes(&buf, true, &[]).expect_err(
3622 "bad-flags OTC must return ATTRIBUTE_FLAGS_ERROR, not Ok or other variant",
3623 );
3624 match err {
3625 DecodeError::UpdateAttributeError { subcode, .. } => {
3626 assert_eq!(
3627 subcode,
3628 update_subcode::ATTRIBUTE_FLAGS_ERROR,
3629 "bad flags {bad_flags:#04x}: expected subcode 4 ATTRIBUTE_FLAGS_ERROR"
3630 );
3631 }
3632 other => panic!("expected UpdateAttributeError, got {other:?}"),
3633 }
3634 }
3635 }
3636
3637 #[test]
3638 fn only_to_customer_partial_bit_preserved_via_unknown() {
3639 let buf = [
3649 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL, attr_type::ONLY_TO_CUSTOMER,
3651 4,
3652 0x00,
3653 0x00,
3654 0xFD,
3655 0xE8, ];
3657 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3658 assert_eq!(decoded.len(), 1);
3659 match &decoded[0] {
3660 PathAttribute::Unknown(raw) => {
3661 assert_eq!(raw.type_code, attr_type::ONLY_TO_CUSTOMER);
3662 assert_eq!(
3663 raw.flags,
3664 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL,
3665 "Partial bit must survive decode for round-trip-faithful re-emission"
3666 );
3667 assert_eq!(raw.data.as_ref(), &[0x00, 0x00, 0xFD, 0xE8][..]);
3668 }
3669 other => panic!(
3670 "Partial-bearing OTC must decode to Unknown (so encode preserves \
3671 Partial via the existing Unknown-encode path); got {other:?}"
3672 ),
3673 }
3674
3675 let mut reencoded = Vec::new();
3679 encode_path_attributes(&decoded, &mut reencoded, true, false);
3680 assert_eq!(
3681 reencoded[0],
3682 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL,
3683 "re-encode must preserve Partial; lost-Partial violates RFC 4271 §5"
3684 );
3685 assert_eq!(reencoded[1], attr_type::ONLY_TO_CUSTOMER);
3686 assert_eq!(reencoded[2], 4);
3687 assert_eq!(&reencoded[3..7], &[0x00, 0x00, 0xFD, 0xE8][..]);
3688 }
3689
3690 #[test]
3691 fn only_to_customer_locally_constructed_emits_canonical_flags() {
3692 let attrs = vec![PathAttribute::OnlyToCustomer(65000)];
3695 let mut buf = Vec::new();
3696 encode_path_attributes(&attrs, &mut buf, true, false);
3697 assert_eq!(
3698 buf[0],
3699 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3700 "locally-constructed OTC must emit 0xC0, never 0xE0"
3701 );
3702 }
3703}