1use std::fmt;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4use bytes::Bytes;
5
6use crate::capability::{Afi, Safi};
7use crate::constants::{as_path_segment, attr_flags, attr_type};
8use crate::error::DecodeError;
9use crate::nlri::{NlriEntry, Prefix};
10use crate::notification::update_subcode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u8)]
15pub enum Origin {
16 Igp = 0,
18 Egp = 1,
20 Incomplete = 2,
22}
23
24impl Origin {
25 #[must_use]
27 pub fn from_u8(value: u8) -> Option<Self> {
28 match value {
29 0 => Some(Self::Igp),
30 1 => Some(Self::Egp),
31 2 => Some(Self::Incomplete),
32 _ => None,
33 }
34 }
35}
36
37impl std::fmt::Display for Origin {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Igp => write!(f, "IGP"),
41 Self::Egp => write!(f, "EGP"),
42 Self::Incomplete => write!(f, "INCOMPLETE"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub enum AsPathSegment {
50 AsSet(Vec<u32>),
52 AsSequence(Vec<u32>),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct AsPath {
59 pub segments: Vec<AsPathSegment>,
61}
62
63impl AsPath {
64 #[must_use]
67 pub fn len(&self) -> usize {
68 self.segments
69 .iter()
70 .map(|seg| match seg {
71 AsPathSegment::AsSequence(asns) => asns.len(),
72 AsPathSegment::AsSet(_) => 1,
73 })
74 .sum()
75 }
76
77 #[must_use]
79 pub fn is_empty(&self) -> bool {
80 self.segments.is_empty()
81 }
82
83 #[must_use]
86 pub fn contains_asn(&self, asn: u32) -> bool {
87 self.segments.iter().any(|seg| match seg {
88 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => asns.contains(&asn),
89 })
90 }
91
92 #[must_use]
98 pub fn origin_asn(&self) -> Option<u32> {
99 self.segments.iter().rev().find_map(|seg| match seg {
100 AsPathSegment::AsSequence(asns) => asns.last().copied(),
101 AsPathSegment::AsSet(_) => None,
102 })
103 }
104
105 #[must_use]
109 pub fn all_private(&self) -> bool {
110 let mut count = 0;
111 for seg in &self.segments {
112 match seg {
113 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => {
114 for asn in asns {
115 count += 1;
116 if !is_private_asn(*asn) {
117 return false;
118 }
119 }
120 }
121 }
122 }
123 count > 0
124 }
125
126 #[must_use]
134 pub fn to_aspath_string(&self) -> String {
135 let mut parts = Vec::new();
136 for seg in &self.segments {
137 match seg {
138 AsPathSegment::AsSequence(asns) => {
139 for asn in asns {
140 parts.push(asn.to_string());
141 }
142 }
143 AsPathSegment::AsSet(asns) => {
144 let inner: Vec<String> = asns.iter().map(ToString::to_string).collect();
145 parts.push(format!("{{{}}}", inner.join(" ")));
146 }
147 }
148 }
149 parts.join(" ")
150 }
151}
152
153#[must_use]
159pub fn is_private_asn(asn: u32) -> bool {
160 (64512..=65534).contains(&asn) || (4_200_000_000..=4_294_967_294).contains(&asn)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub struct MpReachNlri {
169 pub afi: Afi,
171 pub safi: Safi,
173 pub next_hop: IpAddr,
181 pub link_local_next_hop: Option<Ipv6Addr>,
186 pub announced: Vec<NlriEntry>,
188 pub flowspec_announced: Vec<crate::flowspec::FlowSpecRule>,
190 pub evpn_announced: Vec<crate::evpn::EvpnRoute>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub struct MpUnreachNlri {
200 pub afi: Afi,
202 pub safi: Safi,
204 pub withdrawn: Vec<NlriEntry>,
206 pub flowspec_withdrawn: Vec<crate::flowspec::FlowSpecRule>,
208 pub evpn_withdrawn: Vec<crate::evpn::EvpnRoute>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub struct ExtendedCommunity(u64);
218
219impl ExtendedCommunity {
220 #[must_use]
222 pub fn new(raw: u64) -> Self {
223 Self(raw)
224 }
225
226 #[must_use]
228 pub fn as_u64(self) -> u64 {
229 self.0
230 }
231
232 #[must_use]
234 pub fn type_byte(self) -> u8 {
235 (self.0 >> 56) as u8
236 }
237
238 #[must_use]
240 pub fn subtype(self) -> u8 {
241 self.0.to_be_bytes()[1]
242 }
243
244 #[must_use]
246 pub fn is_transitive(self) -> bool {
247 self.type_byte() & 0x40 == 0
248 }
249
250 #[must_use]
252 pub fn value_bytes(self) -> [u8; 6] {
253 let b = self.0.to_be_bytes();
254 [b[2], b[3], b[4], b[5], b[6], b[7]]
255 }
256
257 #[must_use]
268 pub fn route_target(self) -> Option<(u32, u32)> {
269 if self.subtype() != 0x02 {
270 return None;
271 }
272 self.decode_two_part()
273 }
274
275 #[must_use]
282 pub fn route_origin(self) -> Option<(u32, u32)> {
283 if self.subtype() != 0x03 {
284 return None;
285 }
286 self.decode_two_part()
287 }
288
289 #[must_use]
307 pub fn as_bgp_encapsulation(self) -> Option<u16> {
308 if self.type_byte() & 0x3F != 0x03 || self.subtype() != 0x0C {
309 return None;
310 }
311 let v = self.value_bytes();
312 Some(u16::from_be_bytes([v[4], v[5]]))
313 }
314
315 #[must_use]
319 pub fn bgp_encapsulation(tunnel_type: u16) -> Self {
320 let tt = tunnel_type.to_be_bytes();
321 let raw = u64::from_be_bytes([0x03, 0x0C, 0, 0, 0, 0, tt[0], tt[1]]);
322 Self(raw)
323 }
324
325 #[must_use]
332 pub fn as_mac_mobility(self) -> Option<(bool, u32)> {
333 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x00 {
334 return None;
335 }
336 let v = self.value_bytes();
337 let sticky = (v[0] & 0x01) != 0;
338 let seq = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
339 Some((sticky, seq))
340 }
341
342 #[must_use]
344 pub fn mac_mobility(sticky: bool, sequence: u32) -> Self {
345 let flags = u8::from(sticky);
346 let s = sequence.to_be_bytes();
347 let raw = u64::from_be_bytes([0x06, 0x00, flags, 0, s[0], s[1], s[2], s[3]]);
348 Self(raw)
349 }
350
351 #[must_use]
357 pub fn as_esi_label(self) -> Option<(bool, u32)> {
358 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x01 {
359 return None;
360 }
361 let v = self.value_bytes();
362 let single_active = (v[0] & 0x01) != 0;
363 let label = (u32::from(v[3]) << 16) | (u32::from(v[4]) << 8) | u32::from(v[5]);
364 Some((single_active, label))
365 }
366
367 #[must_use]
371 pub fn esi_label(single_active: bool, label: u32) -> Self {
372 let flags = u8::from(single_active);
373 let l = label & 0x00FF_FFFF;
374 #[expect(clippy::cast_possible_truncation)]
375 let raw = u64::from_be_bytes([
376 0x06,
377 0x01,
378 flags,
379 0,
380 0,
381 (l >> 16) as u8,
382 (l >> 8) as u8,
383 l as u8,
384 ]);
385 Self(raw)
386 }
387
388 #[must_use]
394 pub fn as_es_import_rt(self) -> Option<[u8; 6]> {
395 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x02 {
396 return None;
397 }
398 Some(self.value_bytes())
399 }
400
401 #[must_use]
403 pub fn es_import_rt(mac: [u8; 6]) -> Self {
404 let raw = u64::from_be_bytes([0x06, 0x02, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
405 Self(raw)
406 }
407
408 #[must_use]
413 pub fn as_router_mac(self) -> Option<[u8; 6]> {
414 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x03 {
415 return None;
416 }
417 Some(self.value_bytes())
418 }
419
420 #[must_use]
422 pub fn router_mac(mac: [u8; 6]) -> Self {
423 let raw = u64::from_be_bytes([0x06, 0x03, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
424 Self(raw)
425 }
426
427 #[must_use]
434 pub fn as_default_gateway(self) -> bool {
435 self.type_byte() & 0x3F == 0x03 && self.subtype() == 0x0D && self.value_bytes() == [0u8; 6]
436 }
437
438 #[must_use]
440 pub fn default_gateway() -> Self {
441 let raw = u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0]);
442 Self(raw)
443 }
444
445 fn decode_two_part(self) -> Option<(u32, u32)> {
451 let v = self.value_bytes();
452 let t = self.type_byte() & 0x3F; match t {
454 0x00 => {
456 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
457 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
458 Some((global, local))
459 }
460 0x01 | 0x02 => {
462 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
463 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
464 Some((global, local))
465 }
466 _ => None,
467 }
468 }
469}
470
471impl fmt::Display for ExtendedCommunity {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
474 if let Some((g, l)) = self.route_target() {
475 if is_ipv4 {
476 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
477 } else {
478 write!(f, "RT:{g}:{l}")
479 }
480 } else if let Some((g, l)) = self.route_origin() {
481 if is_ipv4 {
482 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
483 } else {
484 write!(f, "RO:{g}:{l}")
485 }
486 } else {
487 write!(f, "0x{:016x}", self.0)
488 }
489 }
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
496pub struct LargeCommunity {
497 pub global_admin: u32,
499 pub local_data1: u32,
501 pub local_data2: u32,
503}
504
505impl LargeCommunity {
506 #[must_use]
508 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
509 Self {
510 global_admin,
511 local_data1,
512 local_data2,
513 }
514 }
515}
516
517impl fmt::Display for LargeCommunity {
518 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519 write!(
520 f,
521 "{}:{}:{}",
522 self.global_admin, self.local_data1, self.local_data2
523 )
524 }
525}
526
527#[derive(Debug, Clone, PartialEq, Eq, Hash)]
532pub enum PathAttribute {
533 Origin(Origin),
535 AsPath(AsPath),
537 NextHop(Ipv4Addr),
539 LocalPref(u32),
541 Med(u32),
543 Communities(Vec<u32>),
545 ExtendedCommunities(Vec<ExtendedCommunity>),
547 LargeCommunities(Vec<LargeCommunity>),
549 OriginatorId(Ipv4Addr),
551 ClusterList(Vec<Ipv4Addr>),
553 MpReachNlri(MpReachNlri),
555 MpUnreachNlri(MpUnreachNlri),
557 PmsiTunnel(crate::pmsi::PmsiTunnel),
560 Unknown(RawAttribute),
562}
563
564impl PathAttribute {
565 #[must_use]
567 pub fn type_code(&self) -> u8 {
568 match self {
569 Self::Origin(_) => attr_type::ORIGIN,
570 Self::AsPath(_) => attr_type::AS_PATH,
571 Self::NextHop(_) => attr_type::NEXT_HOP,
572 Self::LocalPref(_) => attr_type::LOCAL_PREF,
573 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
574 Self::Communities(_) => attr_type::COMMUNITIES,
575 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
576 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
577 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
578 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
579 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
580 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
581 Self::PmsiTunnel(_) => attr_type::PMSI_TUNNEL,
582 Self::Unknown(raw) => raw.type_code,
583 }
584 }
585
586 #[must_use]
588 pub fn flags(&self) -> u8 {
589 match self {
590 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
591 attr_flags::TRANSITIVE
592 }
593 Self::Med(_)
594 | Self::OriginatorId(_)
595 | Self::ClusterList(_)
596 | Self::MpReachNlri(_)
597 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
598 Self::Communities(_)
599 | Self::ExtendedCommunities(_)
600 | Self::LargeCommunities(_)
601 | Self::PmsiTunnel(_) => attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
602 Self::Unknown(raw) => raw.flags,
603 }
604 }
605}
606
607#[derive(Debug, Clone, PartialEq, Eq, Hash)]
612pub struct RawAttribute {
613 pub flags: u8,
615 pub type_code: u8,
617 pub data: Bytes,
619}
620
621pub fn decode_path_attributes(
632 mut buf: &[u8],
633 four_octet_as: bool,
634 add_path_families: &[(Afi, Safi)],
635) -> Result<Vec<PathAttribute>, DecodeError> {
636 let mut attrs = Vec::new();
637
638 while !buf.is_empty() {
639 if buf.len() < 2 {
641 return Err(DecodeError::MalformedField {
642 message_type: "UPDATE",
643 detail: "truncated attribute header".to_string(),
644 });
645 }
646
647 let flags = buf[0];
648 let type_code = buf[1];
649 buf = &buf[2..];
650
651 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
652 let value_len = if extended {
653 if buf.len() < 2 {
654 return Err(DecodeError::MalformedField {
655 message_type: "UPDATE",
656 detail: "truncated extended-length attribute".to_string(),
657 });
658 }
659 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
660 buf = &buf[2..];
661 len
662 } else {
663 if buf.is_empty() {
664 return Err(DecodeError::MalformedField {
665 message_type: "UPDATE",
666 detail: "truncated attribute length".to_string(),
667 });
668 }
669 let len = buf[0] as usize;
670 buf = &buf[1..];
671 len
672 };
673
674 if buf.len() < value_len {
675 return Err(DecodeError::MalformedField {
676 message_type: "UPDATE",
677 detail: format!(
678 "attribute type {type_code} value truncated: need {value_len}, have {}",
679 buf.len()
680 ),
681 });
682 }
683
684 let value = &buf[..value_len];
685 buf = &buf[value_len..];
686
687 let attr =
688 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
689 attrs.push(attr);
690 }
691
692 Ok(attrs)
693}
694
695#[expect(clippy::too_many_lines)]
697fn decode_attribute_value(
698 flags: u8,
699 type_code: u8,
700 value: &[u8],
701 four_octet_as: bool,
702 add_path_families: &[(Afi, Safi)],
703) -> Result<PathAttribute, DecodeError> {
704 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
706 if let Some(expected) = expected_flags(type_code)
707 && (flags & flags_mask) != expected
708 {
709 return Err(DecodeError::UpdateAttributeError {
710 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
711 data: attr_error_data(flags, type_code, value),
712 detail: format!(
713 "type {} flags {:#04x} (expected {:#04x})",
714 type_code,
715 flags & flags_mask,
716 expected
717 ),
718 });
719 }
720
721 match type_code {
722 attr_type::ORIGIN => {
723 if value.len() != 1 {
724 return Err(DecodeError::UpdateAttributeError {
725 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
726 data: attr_error_data(flags, type_code, value),
727 detail: format!("ORIGIN length {} (expected 1)", value.len()),
728 });
729 }
730 match Origin::from_u8(value[0]) {
731 Some(origin) => Ok(PathAttribute::Origin(origin)),
732 None => Err(DecodeError::UpdateAttributeError {
733 subcode: update_subcode::INVALID_ORIGIN,
734 data: attr_error_data(flags, type_code, value),
735 detail: format!("invalid ORIGIN value {}", value[0]),
736 }),
737 }
738 }
739
740 attr_type::AS_PATH => {
741 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
742 DecodeError::UpdateAttributeError {
743 subcode: update_subcode::MALFORMED_AS_PATH,
744 data: attr_error_data(flags, type_code, value),
745 detail: e.to_string(),
746 }
747 })?;
748 Ok(PathAttribute::AsPath(AsPath { segments }))
749 }
750
751 attr_type::NEXT_HOP => {
752 if value.len() != 4 {
753 return Err(DecodeError::UpdateAttributeError {
754 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
755 data: attr_error_data(flags, type_code, value),
756 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
757 });
758 }
759 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
760 Ok(PathAttribute::NextHop(addr))
761 }
762
763 attr_type::MULTI_EXIT_DISC => {
764 if value.len() != 4 {
765 return Err(DecodeError::UpdateAttributeError {
766 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
767 data: attr_error_data(flags, type_code, value),
768 detail: format!("MED length {} (expected 4)", value.len()),
769 });
770 }
771 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
772 Ok(PathAttribute::Med(med))
773 }
774
775 attr_type::LOCAL_PREF => {
776 if value.len() != 4 {
777 return Err(DecodeError::UpdateAttributeError {
778 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
779 data: attr_error_data(flags, type_code, value),
780 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
781 });
782 }
783 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
784 Ok(PathAttribute::LocalPref(lp))
785 }
786
787 attr_type::COMMUNITIES => {
788 if !value.len().is_multiple_of(4) {
789 return Err(DecodeError::UpdateAttributeError {
790 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
791 data: attr_error_data(flags, type_code, value),
792 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
793 });
794 }
795 let communities = value
796 .chunks_exact(4)
797 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
798 .collect();
799 Ok(PathAttribute::Communities(communities))
800 }
801
802 attr_type::EXTENDED_COMMUNITIES => {
803 if !value.len().is_multiple_of(8) {
804 return Err(DecodeError::UpdateAttributeError {
805 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
806 data: attr_error_data(flags, type_code, value),
807 detail: format!(
808 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
809 value.len()
810 ),
811 });
812 }
813 let communities = value
814 .chunks_exact(8)
815 .map(|c| {
816 ExtendedCommunity::new(u64::from_be_bytes([
817 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
818 ]))
819 })
820 .collect();
821 Ok(PathAttribute::ExtendedCommunities(communities))
822 }
823
824 attr_type::ORIGINATOR_ID => {
825 if value.len() != 4 {
826 return Err(DecodeError::UpdateAttributeError {
827 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
828 data: attr_error_data(flags, type_code, value),
829 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
830 });
831 }
832 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
833 Ok(PathAttribute::OriginatorId(addr))
834 }
835
836 attr_type::CLUSTER_LIST => {
837 if !value.len().is_multiple_of(4) {
838 return Err(DecodeError::UpdateAttributeError {
839 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
840 data: attr_error_data(flags, type_code, value),
841 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
842 });
843 }
844 let ids = value
845 .chunks_exact(4)
846 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
847 .collect();
848 Ok(PathAttribute::ClusterList(ids))
849 }
850
851 attr_type::LARGE_COMMUNITIES => {
852 if value.is_empty() || !value.len().is_multiple_of(12) {
853 return Err(DecodeError::UpdateAttributeError {
854 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
855 data: attr_error_data(flags, type_code, value),
856 detail: format!(
857 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
858 value.len()
859 ),
860 });
861 }
862 let communities = value
863 .chunks_exact(12)
864 .map(|c| {
865 LargeCommunity::new(
866 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
867 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
868 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
869 )
870 })
871 .collect();
872 Ok(PathAttribute::LargeCommunities(communities))
873 }
874
875 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
876 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
877
878 attr_type::PMSI_TUNNEL => {
879 let pmsi = crate::pmsi::PmsiTunnel::decode(value)?;
880 Ok(PathAttribute::PmsiTunnel(pmsi))
881 }
882
883 _ => Ok(PathAttribute::Unknown(RawAttribute {
885 flags,
886 type_code,
887 data: Bytes::copy_from_slice(value),
888 })),
889 }
890}
891
892#[expect(clippy::too_many_lines)]
897fn decode_mp_reach_nlri(
898 value: &[u8],
899 add_path_families: &[(Afi, Safi)],
900) -> Result<PathAttribute, DecodeError> {
901 if value.len() < 5 {
902 return Err(DecodeError::MalformedField {
903 message_type: "UPDATE",
904 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
905 });
906 }
907
908 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
909 let safi_raw = value[2];
910 let nh_len = value[3] as usize;
911
912 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
913 message_type: "UPDATE",
914 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
915 })?;
916 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
917 message_type: "UPDATE",
918 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
919 })?;
920
921 if value.len() < 4 + nh_len + 1 {
923 return Err(DecodeError::MalformedField {
924 message_type: "UPDATE",
925 detail: format!(
926 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
927 value.len()
928 ),
929 });
930 }
931
932 let nh_bytes = &value[4..4 + nh_len];
933 let mut link_local_next_hop: Option<Ipv6Addr> = None;
935 let next_hop = if safi == Safi::FlowSpec {
936 if nh_len != 0 {
937 return Err(DecodeError::MalformedField {
938 message_type: "UPDATE",
939 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
940 });
941 }
942 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
943 } else {
944 match afi {
945 Afi::Ipv4 => match nh_len {
946 4 => IpAddr::V4(Ipv4Addr::new(
947 nh_bytes[0],
948 nh_bytes[1],
949 nh_bytes[2],
950 nh_bytes[3],
951 )),
952 16 | 32 => {
953 let mut octets = [0u8; 16];
954 octets.copy_from_slice(&nh_bytes[..16]);
955 if nh_len == 32 {
956 let mut ll = [0u8; 16];
957 ll.copy_from_slice(&nh_bytes[16..32]);
958 link_local_next_hop = Some(Ipv6Addr::from(ll));
959 }
960 IpAddr::V6(Ipv6Addr::from(octets))
961 }
962 _ => {
963 return Err(DecodeError::MalformedField {
964 message_type: "UPDATE",
965 detail: format!(
966 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
967 ),
968 });
969 }
970 },
971 Afi::Ipv6 => {
972 if nh_len != 16 && nh_len != 32 {
973 return Err(DecodeError::MalformedField {
974 message_type: "UPDATE",
975 detail: format!(
976 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
977 ),
978 });
979 }
980 let mut octets = [0u8; 16];
981 octets.copy_from_slice(&nh_bytes[..16]);
982 if nh_len == 32 {
983 let mut ll = [0u8; 16];
984 ll.copy_from_slice(&nh_bytes[16..32]);
985 link_local_next_hop = Some(Ipv6Addr::from(ll));
986 }
987 IpAddr::V6(Ipv6Addr::from(octets))
988 }
989 Afi::L2Vpn => match nh_len {
990 4 => IpAddr::V4(Ipv4Addr::new(
991 nh_bytes[0],
992 nh_bytes[1],
993 nh_bytes[2],
994 nh_bytes[3],
995 )),
996 16 => {
997 let mut octets = [0u8; 16];
998 octets.copy_from_slice(&nh_bytes[..16]);
999 IpAddr::V6(Ipv6Addr::from(octets))
1000 }
1001 _ => {
1002 return Err(DecodeError::MalformedField {
1003 message_type: "UPDATE",
1004 detail: format!(
1005 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
1006 ),
1007 });
1008 }
1009 },
1010 }
1011 };
1012
1013 let nlri_start = 4 + nh_len + 1;
1015 let nlri_bytes = &value[nlri_start..];
1016
1017 if safi == Safi::FlowSpec {
1019 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1020 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1021 afi,
1022 safi,
1023 next_hop,
1024 link_local_next_hop,
1025 announced: vec![],
1026 flowspec_announced: flowspec_rules,
1027 evpn_announced: vec![],
1028 }));
1029 }
1030
1031 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1033 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1034 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1035 afi,
1036 safi,
1037 next_hop,
1038 link_local_next_hop,
1039 announced: vec![],
1040 flowspec_announced: vec![],
1041 evpn_announced: routes,
1042 }));
1043 }
1044
1045 if safi == Safi::Evpn {
1049 return Err(DecodeError::MalformedField {
1050 message_type: "UPDATE",
1051 detail: format!(
1052 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1053 afi as u16
1054 ),
1055 });
1056 }
1057
1058 let add_path = add_path_families.contains(&(afi, safi));
1059 let announced = match (afi, add_path) {
1060 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1061 .into_iter()
1062 .map(|p| NlriEntry {
1063 path_id: 0,
1064 prefix: Prefix::V4(p),
1065 })
1066 .collect(),
1067 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1068 .into_iter()
1069 .map(|e| NlriEntry {
1070 path_id: e.path_id,
1071 prefix: Prefix::V4(e.prefix),
1072 })
1073 .collect(),
1074 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1075 .into_iter()
1076 .map(|p| NlriEntry {
1077 path_id: 0,
1078 prefix: Prefix::V6(p),
1079 })
1080 .collect(),
1081 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1082 (Afi::L2Vpn, _) => {
1083 return Err(DecodeError::MalformedField {
1084 message_type: "UPDATE",
1085 detail: format!(
1086 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1087 safi as u8
1088 ),
1089 });
1090 }
1091 };
1092
1093 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1094 afi,
1095 safi,
1096 next_hop,
1097 link_local_next_hop,
1098 announced,
1099 flowspec_announced: vec![],
1100 evpn_announced: vec![],
1101 }))
1102}
1103
1104fn decode_mp_unreach_nlri(
1109 value: &[u8],
1110 add_path_families: &[(Afi, Safi)],
1111) -> Result<PathAttribute, DecodeError> {
1112 if value.len() < 3 {
1113 return Err(DecodeError::MalformedField {
1114 message_type: "UPDATE",
1115 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1116 });
1117 }
1118
1119 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1120 let safi_raw = value[2];
1121
1122 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1123 message_type: "UPDATE",
1124 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1125 })?;
1126 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1127 message_type: "UPDATE",
1128 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1129 })?;
1130
1131 let withdrawn_bytes = &value[3..];
1132
1133 if safi == Safi::FlowSpec {
1135 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1136 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1137 afi,
1138 safi,
1139 withdrawn: vec![],
1140 flowspec_withdrawn: flowspec_rules,
1141 evpn_withdrawn: vec![],
1142 }));
1143 }
1144
1145 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1147 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1148 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1149 afi,
1150 safi,
1151 withdrawn: vec![],
1152 flowspec_withdrawn: vec![],
1153 evpn_withdrawn: routes,
1154 }));
1155 }
1156
1157 if safi == Safi::Evpn {
1161 return Err(DecodeError::MalformedField {
1162 message_type: "UPDATE",
1163 detail: format!(
1164 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1165 afi as u16
1166 ),
1167 });
1168 }
1169
1170 let add_path = add_path_families.contains(&(afi, safi));
1171 let withdrawn = match (afi, add_path) {
1172 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1173 .into_iter()
1174 .map(|p| NlriEntry {
1175 path_id: 0,
1176 prefix: Prefix::V4(p),
1177 })
1178 .collect(),
1179 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1180 .into_iter()
1181 .map(|e| NlriEntry {
1182 path_id: e.path_id,
1183 prefix: Prefix::V4(e.prefix),
1184 })
1185 .collect(),
1186 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1187 .into_iter()
1188 .map(|p| NlriEntry {
1189 path_id: 0,
1190 prefix: Prefix::V6(p),
1191 })
1192 .collect(),
1193 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1194 (Afi::L2Vpn, _) => {
1195 return Err(DecodeError::MalformedField {
1196 message_type: "UPDATE",
1197 detail: format!(
1198 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1199 safi as u8
1200 ),
1201 });
1202 }
1203 };
1204
1205 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1206 afi,
1207 safi,
1208 withdrawn,
1209 flowspec_withdrawn: vec![],
1210 evpn_withdrawn: vec![],
1211 }))
1212}
1213
1214fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1216 let as_size: usize = if four_octet_as { 4 } else { 2 };
1217 let mut segments = Vec::new();
1218
1219 while !buf.is_empty() {
1220 if buf.len() < 2 {
1221 return Err(DecodeError::MalformedField {
1222 message_type: "UPDATE",
1223 detail: "truncated AS_PATH segment header".to_string(),
1224 });
1225 }
1226
1227 let seg_type = buf[0];
1228 let seg_count = buf[1] as usize;
1229 buf = &buf[2..];
1230
1231 let needed = seg_count * as_size;
1232 if buf.len() < needed {
1233 return Err(DecodeError::MalformedField {
1234 message_type: "UPDATE",
1235 detail: format!(
1236 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1237 buf.len()
1238 ),
1239 });
1240 }
1241
1242 let mut asns = Vec::with_capacity(seg_count);
1243 for _ in 0..seg_count {
1244 let asn = if four_octet_as {
1245 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1246 buf = &buf[4..];
1247 v
1248 } else {
1249 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1250 buf = &buf[2..];
1251 v
1252 };
1253 asns.push(asn);
1254 }
1255
1256 match seg_type {
1257 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1258 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1259 _ => {
1260 return Err(DecodeError::MalformedField {
1261 message_type: "UPDATE",
1262 detail: format!("unknown AS_PATH segment type {seg_type}"),
1263 });
1264 }
1265 }
1266 }
1267
1268 Ok(segments)
1269}
1270
1271pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1274 let mut buf = Vec::with_capacity(3 + value.len());
1275 if value.len() > 255 {
1276 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1277 buf.push(type_code);
1278 #[expect(clippy::cast_possible_truncation)]
1279 let len = value.len() as u16;
1280 buf.extend_from_slice(&len.to_be_bytes());
1281 } else {
1282 buf.push(flags);
1283 buf.push(type_code);
1284 #[expect(clippy::cast_possible_truncation)]
1285 buf.push(value.len() as u8);
1286 }
1287 buf.extend_from_slice(value);
1288 buf
1289}
1290
1291fn expected_flags(type_code: u8) -> Option<u8> {
1294 match type_code {
1295 attr_type::ORIGIN
1297 | attr_type::AS_PATH
1298 | attr_type::NEXT_HOP
1299 | attr_type::LOCAL_PREF
1300 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1301 attr_type::MULTI_EXIT_DISC
1304 | attr_type::ORIGINATOR_ID
1305 | attr_type::CLUSTER_LIST
1306 | attr_type::MP_REACH_NLRI
1307 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1308 attr_type::AGGREGATOR
1310 | attr_type::COMMUNITIES
1311 | attr_type::EXTENDED_COMMUNITIES
1312 | attr_type::LARGE_COMMUNITIES
1313 | attr_type::PMSI_TUNNEL => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1314 _ => None,
1315 }
1316}
1317
1318#[expect(
1326 clippy::too_many_lines,
1327 reason = "dispatch arms are inherently O(variants); each new path attribute adds a small block"
1328)]
1329pub fn encode_path_attributes(
1330 attrs: &[PathAttribute],
1331 buf: &mut Vec<u8>,
1332 four_octet_as: bool,
1333 add_path_mp: bool,
1334) {
1335 for attr in attrs {
1336 let mut value = Vec::new();
1337 let flags;
1338 let type_code;
1339
1340 match attr {
1341 PathAttribute::Origin(origin) => {
1342 flags = attr_flags::TRANSITIVE;
1343 type_code = attr_type::ORIGIN;
1344 value.push(*origin as u8);
1345 }
1346 PathAttribute::AsPath(as_path) => {
1347 flags = attr_flags::TRANSITIVE;
1348 type_code = attr_type::AS_PATH;
1349 encode_as_path(as_path, &mut value, four_octet_as);
1350 }
1351 PathAttribute::NextHop(addr) => {
1352 flags = attr_flags::TRANSITIVE;
1353 type_code = attr_type::NEXT_HOP;
1354 value.extend_from_slice(&addr.octets());
1355 }
1356 PathAttribute::Med(med) => {
1357 flags = attr_flags::OPTIONAL;
1358 type_code = attr_type::MULTI_EXIT_DISC;
1359 value.extend_from_slice(&med.to_be_bytes());
1360 }
1361 PathAttribute::LocalPref(lp) => {
1362 flags = attr_flags::TRANSITIVE;
1363 type_code = attr_type::LOCAL_PREF;
1364 value.extend_from_slice(&lp.to_be_bytes());
1365 }
1366 PathAttribute::Communities(communities) => {
1367 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1368 type_code = attr_type::COMMUNITIES;
1369 for &c in communities {
1370 value.extend_from_slice(&c.to_be_bytes());
1371 }
1372 }
1373 PathAttribute::ExtendedCommunities(communities) => {
1374 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1375 type_code = attr_type::EXTENDED_COMMUNITIES;
1376 for &c in communities {
1377 value.extend_from_slice(&c.as_u64().to_be_bytes());
1378 }
1379 }
1380 PathAttribute::LargeCommunities(communities) => {
1381 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1382 type_code = attr_type::LARGE_COMMUNITIES;
1383 for &c in communities {
1384 value.extend_from_slice(&c.global_admin.to_be_bytes());
1385 value.extend_from_slice(&c.local_data1.to_be_bytes());
1386 value.extend_from_slice(&c.local_data2.to_be_bytes());
1387 }
1388 }
1389 PathAttribute::OriginatorId(addr) => {
1390 flags = attr_flags::OPTIONAL;
1391 type_code = attr_type::ORIGINATOR_ID;
1392 value.extend_from_slice(&addr.octets());
1393 }
1394 PathAttribute::ClusterList(ids) => {
1395 flags = attr_flags::OPTIONAL;
1396 type_code = attr_type::CLUSTER_LIST;
1397 for id in ids {
1398 value.extend_from_slice(&id.octets());
1399 }
1400 }
1401 PathAttribute::MpReachNlri(mp) => {
1402 flags = attr_flags::OPTIONAL;
1403 type_code = attr_type::MP_REACH_NLRI;
1404 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1405 }
1406 PathAttribute::MpUnreachNlri(mp) => {
1407 flags = attr_flags::OPTIONAL;
1408 type_code = attr_type::MP_UNREACH_NLRI;
1409 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1410 }
1411 PathAttribute::PmsiTunnel(pmsi) => {
1412 (flags, type_code) = (
1414 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
1415 attr_type::PMSI_TUNNEL,
1416 );
1417 pmsi.encode(&mut value);
1418 }
1419 PathAttribute::Unknown(raw) => {
1420 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1424 flags = if (raw.flags & optional_transitive) == optional_transitive {
1425 raw.flags | attr_flags::PARTIAL
1426 } else {
1427 raw.flags
1428 };
1429 type_code = raw.type_code;
1430 value.extend_from_slice(&raw.data);
1431 }
1432 }
1433
1434 if value.len() > 255 {
1436 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1437 buf.push(type_code);
1438 #[expect(clippy::cast_possible_truncation)]
1439 let len = value.len() as u16;
1440 buf.extend_from_slice(&len.to_be_bytes());
1441 } else {
1442 buf.push(flags);
1443 buf.push(type_code);
1444 #[expect(clippy::cast_possible_truncation)]
1445 buf.push(value.len() as u8);
1446 }
1447 buf.extend_from_slice(&value);
1448 }
1449}
1450
1451fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1456 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1457 buf.push(mp.safi as u8);
1458
1459 if mp.safi == Safi::FlowSpec {
1461 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1464 return;
1465 }
1466
1467 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1469 match mp.next_hop {
1470 IpAddr::V4(addr) => {
1471 buf.push(4);
1472 buf.extend_from_slice(&addr.octets());
1473 }
1474 IpAddr::V6(addr) => {
1475 buf.push(16);
1476 buf.extend_from_slice(&addr.octets());
1477 }
1478 }
1479 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1481 return;
1482 }
1483
1484 match (mp.next_hop, mp.link_local_next_hop) {
1485 (IpAddr::V4(addr), _) => {
1486 buf.push(4); buf.extend_from_slice(&addr.octets());
1488 }
1489 (IpAddr::V6(addr), Some(ll)) => {
1490 debug_assert!(
1502 (ll.segments()[0] & 0xffc0) == 0xfe80,
1503 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1504 );
1505 buf.push(32); buf.extend_from_slice(&addr.octets());
1507 buf.extend_from_slice(&ll.octets());
1508 }
1509 (IpAddr::V6(addr), None) => {
1510 buf.push(16); buf.extend_from_slice(&addr.octets());
1512 }
1513 }
1514
1515 buf.push(0); if add_path {
1518 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1519 } else {
1520 for entry in &mp.announced {
1521 match entry.prefix {
1522 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1523 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1524 }
1525 }
1526 }
1527}
1528
1529fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1533 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1534 buf.push(mp.safi as u8);
1535
1536 if mp.safi == Safi::FlowSpec {
1538 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1539 return;
1540 }
1541
1542 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1544 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1545 return;
1546 }
1547
1548 if add_path {
1549 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1550 } else {
1551 for entry in &mp.withdrawn {
1552 match entry.prefix {
1553 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1554 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1555 }
1556 }
1557 }
1558}
1559
1560fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1562 for segment in &as_path.segments {
1563 let (seg_type, asns) = match segment {
1564 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1565 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1566 };
1567 for chunk in asns.chunks(u8::MAX as usize) {
1568 buf.push(seg_type);
1569 #[expect(clippy::cast_possible_truncation)]
1570 buf.push(chunk.len() as u8);
1571 for &asn in chunk {
1572 if four_octet_as {
1573 buf.extend_from_slice(&asn.to_be_bytes());
1574 } else {
1575 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1578 buf.extend_from_slice(&as2.to_be_bytes());
1579 }
1580 }
1581 }
1582 }
1583}
1584
1585#[cfg(test)]
1586mod tests {
1587 use super::*;
1588
1589 #[test]
1590 fn mp_reach_evpn_attribute_roundtrip() {
1591 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1592
1593 let mp = MpReachNlri {
1594 afi: Afi::L2Vpn,
1595 safi: Safi::Evpn,
1596 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1597 link_local_next_hop: None,
1598 announced: vec![],
1599 flowspec_announced: vec![],
1600 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1601 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1602 ethernet_tag: EthernetTagId(100),
1603 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1604 })],
1605 };
1606 let attr = PathAttribute::MpReachNlri(mp);
1607
1608 let mut buf = Vec::new();
1609 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1610 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1611 assert_eq!(decoded.len(), 1);
1612 assert_eq!(attr, decoded[0]);
1613
1614 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1615 panic!("not MP_REACH after decode");
1616 };
1617 assert_eq!(dec.afi, Afi::L2Vpn);
1618 assert_eq!(dec.safi, Safi::Evpn);
1619 assert_eq!(dec.evpn_announced.len(), 1);
1620 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1621 }
1622
1623 #[test]
1634 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1635 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1636
1637 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1638 let mp = MpReachNlri {
1639 afi: Afi::L2Vpn,
1640 safi: Safi::Evpn,
1641 next_hop: IpAddr::V6(vtep_v6),
1642 link_local_next_hop: None,
1643 announced: vec![],
1644 flowspec_announced: vec![],
1645 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1646 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1647 ethernet_tag: EthernetTagId(100),
1648 originator_ip: IpAddr::V6(vtep_v6),
1649 })],
1650 };
1651 let attr = PathAttribute::MpReachNlri(mp.clone());
1652
1653 let mut buf = Vec::new();
1654 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1655
1656 let extended = (buf[0] & 0x10) != 0;
1667 let value_off = if extended { 4 } else { 3 };
1668 assert_eq!(
1669 buf[value_off + 3],
1670 16,
1671 "EVPN IPv6 NH-Len must be 16, not 32"
1672 );
1673 assert_eq!(
1674 &buf[value_off + 4..value_off + 20],
1675 &vtep_v6.octets(),
1676 "encoded VTEP next-hop bytes must match the input"
1677 );
1678
1679 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1680 assert_eq!(decoded.len(), 1);
1681 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1682
1683 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1684 panic!("not MP_REACH after decode");
1685 };
1686 assert_eq!(dec.afi, Afi::L2Vpn);
1687 assert_eq!(dec.safi, Safi::Evpn);
1688 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1689 assert!(
1690 dec.link_local_next_hop.is_none(),
1691 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1692 );
1693 assert_eq!(dec.evpn_announced.len(), 1);
1694 match &dec.evpn_announced[0] {
1695 EvpnRoute::Imet(imet) => {
1696 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1697 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1698 }
1699 other => panic!("expected IMET, got {other:?}"),
1700 }
1701 }
1702
1703 #[test]
1710 fn mp_reach_evpn_rejects_32byte_next_hop() {
1711 let mut attr = vec![0x80u8, 14, 37];
1717 attr.extend_from_slice(&[
1718 0x00, 0x19, 0x46, 0x20, ]);
1722 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1726 match err {
1727 DecodeError::MalformedField { detail, .. } => {
1728 assert!(
1729 detail.contains("L2VPN next-hop length 32"),
1730 "expected L2VPN NH-Len rejection, got: {detail}"
1731 );
1732 }
1733 other => panic!("expected MalformedField, got: {other:?}"),
1734 }
1735 }
1736
1737 #[test]
1738 fn mp_unreach_evpn_attribute_roundtrip() {
1739 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1740
1741 let mp = MpUnreachNlri {
1742 afi: Afi::L2Vpn,
1743 safi: Safi::Evpn,
1744 withdrawn: vec![],
1745 flowspec_withdrawn: vec![],
1746 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1747 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1748 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1749 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1750 })],
1751 };
1752 let attr = PathAttribute::MpUnreachNlri(mp);
1753 let mut buf = Vec::new();
1754 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1755 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1756 assert_eq!(decoded.len(), 1);
1757 assert_eq!(attr, decoded[0]);
1758 }
1759
1760 #[test]
1763 fn ext_comm_bgp_encapsulation_vxlan() {
1764 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1766 assert_eq!(c.subtype(), 0x0C);
1767 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1768 let b = c.as_u64().to_be_bytes();
1770 assert_eq!(b[2..6], [0, 0, 0, 0]);
1771 assert_eq!(&b[6..8], &[0, 8]);
1772 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1774 }
1775
1776 #[test]
1777 fn ext_comm_mac_mobility_sticky_and_sequence() {
1778 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1779 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1780 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1781 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1782 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1784 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1785 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1786 }
1787
1788 #[test]
1789 fn ext_comm_esi_label_flags_and_label() {
1790 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1791 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1792 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1793 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1794 }
1795
1796 #[test]
1797 fn ext_comm_es_import_rt_mac() {
1798 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1799 let e = ExtendedCommunity::es_import_rt(mac);
1800 assert_eq!(e.as_es_import_rt(), Some(mac));
1801 assert_eq!(e.type_byte(), 0x06);
1802 assert_eq!(e.subtype(), 0x02);
1803 }
1804
1805 #[test]
1806 fn ext_comm_router_mac() {
1807 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1808 let e = ExtendedCommunity::router_mac(mac);
1809 assert_eq!(e.as_router_mac(), Some(mac));
1810 }
1811
1812 #[test]
1813 fn ext_comm_default_gateway_flag_only() {
1814 let d = ExtendedCommunity::default_gateway();
1815 assert!(d.as_default_gateway());
1816 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1818 }
1819
1820 #[test]
1824 fn ext_comm_default_gateway_rejects_nonzero_value() {
1825 let malformed =
1827 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1828 assert!(
1829 !malformed.as_default_gateway(),
1830 "default-gateway accessor must require all-zero value bytes"
1831 );
1832 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1834 }
1835
1836 #[test]
1837 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1838 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1840 assert_eq!(rt.as_mac_mobility(), None);
1841 assert_eq!(rt.as_esi_label(), None);
1842 assert_eq!(rt.as_es_import_rt(), None);
1843 assert_eq!(rt.as_router_mac(), None);
1844 assert!(!rt.as_default_gateway());
1845 }
1846
1847 #[test]
1848 fn origin_from_u8_roundtrip() {
1849 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1850 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1851 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1852 assert_eq!(Origin::from_u8(3), None);
1853 }
1854
1855 #[test]
1856 fn origin_ordering() {
1857 assert!(Origin::Igp < Origin::Egp);
1858 assert!(Origin::Egp < Origin::Incomplete);
1859 }
1860
1861 #[test]
1862 fn as_path_length_calculation() {
1863 let path = AsPath {
1864 segments: vec![
1865 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1866 AsPathSegment::AsSet(vec![65004, 65005]),
1867 ],
1868 };
1869 assert_eq!(path.len(), 4);
1871 }
1872
1873 #[test]
1874 fn as_path_empty() {
1875 let path = AsPath { segments: vec![] };
1876 assert!(path.is_empty());
1877 assert_eq!(path.len(), 0);
1878 }
1879
1880 #[test]
1881 fn contains_asn_in_sequence() {
1882 let path = AsPath {
1883 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1884 };
1885 assert!(path.contains_asn(65002));
1886 assert!(!path.contains_asn(65004));
1887 }
1888
1889 #[test]
1890 fn contains_asn_in_set() {
1891 let path = AsPath {
1892 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1893 };
1894 assert!(path.contains_asn(65005));
1895 assert!(!path.contains_asn(65001));
1896 }
1897
1898 #[test]
1899 fn contains_asn_multiple_segments() {
1900 let path = AsPath {
1901 segments: vec![
1902 AsPathSegment::AsSequence(vec![65001, 65002]),
1903 AsPathSegment::AsSet(vec![65003]),
1904 ],
1905 };
1906 assert!(path.contains_asn(65001));
1907 assert!(path.contains_asn(65003));
1908 assert!(!path.contains_asn(65004));
1909 }
1910
1911 #[test]
1912 fn contains_asn_empty_path() {
1913 let path = AsPath { segments: vec![] };
1914 assert!(!path.contains_asn(65001));
1915 }
1916
1917 #[test]
1918 fn is_private_asn_boundaries() {
1919 assert!(!is_private_asn(64_511));
1921 assert!(is_private_asn(64_512));
1922 assert!(is_private_asn(65_534));
1923 assert!(!is_private_asn(65_535));
1924
1925 assert!(!is_private_asn(4_199_999_999));
1927 assert!(is_private_asn(4_200_000_000));
1928 assert!(is_private_asn(4_294_967_294));
1929 assert!(!is_private_asn(4_294_967_295));
1930 }
1931
1932 #[test]
1933 fn all_private_empty_path_is_false() {
1934 let path = AsPath { segments: vec![] };
1935 assert!(!path.all_private());
1936 }
1937
1938 #[test]
1939 fn all_private_mixed_segments() {
1940 let path = AsPath {
1941 segments: vec![
1942 AsPathSegment::AsSet(vec![64_512, 65_000]),
1943 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1944 ],
1945 };
1946 assert!(path.all_private());
1947
1948 let non_private = AsPath {
1949 segments: vec![
1950 AsPathSegment::AsSet(vec![64_512, 65_000]),
1951 AsPathSegment::AsSequence(vec![65_535]),
1952 ],
1953 };
1954 assert!(!non_private.all_private());
1955 }
1956
1957 #[test]
1958 fn decode_origin_igp() {
1959 let buf = [0x40, 0x01, 0x01, 0x00];
1961 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1962 assert_eq!(attrs.len(), 1);
1963 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1964 }
1965
1966 #[test]
1967 fn decode_origin_egp() {
1968 let buf = [0x40, 0x01, 0x01, 0x01];
1969 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1970 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1971 }
1972
1973 #[test]
1974 fn decode_origin_invalid_value() {
1975 let buf = [0x40, 0x01, 0x01, 0x05];
1977 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1978 match &err {
1979 DecodeError::UpdateAttributeError { subcode, .. } => {
1980 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1981 }
1982 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1983 }
1984 }
1985
1986 #[test]
1987 fn decode_next_hop() {
1988 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1990 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1991 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1992 }
1993
1994 #[test]
1995 fn decode_med() {
1996 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1998 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1999 assert_eq!(attrs[0], PathAttribute::Med(100));
2000 }
2001
2002 #[test]
2003 fn decode_local_pref() {
2004 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
2006 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2007 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
2008 }
2009
2010 #[test]
2011 fn decode_as_path_4byte() {
2012 let buf = [
2015 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2020 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2021 assert_eq!(
2022 attrs[0],
2023 PathAttribute::AsPath(AsPath {
2024 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2025 })
2026 );
2027 }
2028
2029 #[test]
2030 fn decode_as_path_2byte() {
2031 let buf = [
2034 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2039 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2040 assert_eq!(
2041 attrs[0],
2042 PathAttribute::AsPath(AsPath {
2043 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2044 })
2045 );
2046 }
2047
2048 #[test]
2049 fn decode_unknown_attribute_preserved() {
2050 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2052 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2053 assert_eq!(
2054 attrs[0],
2055 PathAttribute::Unknown(RawAttribute {
2056 flags: 0xC0,
2057 type_code: 99,
2058 data: Bytes::from_static(&[1, 2, 3]),
2059 })
2060 );
2061 }
2062
2063 #[test]
2064 fn decode_atomic_aggregate_as_unknown() {
2065 let buf = [0x40, 0x06, 0x00];
2067 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2068 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2069 }
2070
2071 #[test]
2072 fn decode_extended_length() {
2073 let buf = [
2076 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2081 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2082 assert_eq!(
2083 attrs[0],
2084 PathAttribute::AsPath(AsPath {
2085 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2086 })
2087 );
2088 }
2089
2090 #[test]
2091 fn decode_multiple_attributes() {
2092 let mut buf = Vec::new();
2093 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2095 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2097 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2099
2100 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2101 assert_eq!(attrs.len(), 3);
2102 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2103 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2104 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2105 }
2106
2107 #[test]
2108 fn roundtrip_attributes_4byte() {
2109 let attrs = vec![
2110 PathAttribute::Origin(Origin::Igp),
2111 PathAttribute::AsPath(AsPath {
2112 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2113 }),
2114 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2115 PathAttribute::Med(100),
2116 PathAttribute::LocalPref(200),
2117 ];
2118
2119 let mut buf = Vec::new();
2120 encode_path_attributes(&attrs, &mut buf, true, false);
2121 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2122 assert_eq!(decoded, attrs);
2123 }
2124
2125 #[test]
2126 fn roundtrip_attributes_2byte() {
2127 let attrs = vec![
2128 PathAttribute::Origin(Origin::Egp),
2129 PathAttribute::AsPath(AsPath {
2130 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2131 }),
2132 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2133 ];
2134
2135 let mut buf = Vec::new();
2136 encode_path_attributes(&attrs, &mut buf, false, false);
2137 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2138 assert_eq!(decoded, attrs);
2139 }
2140
2141 #[test]
2142 fn reject_truncated_attribute_header() {
2143 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2145 }
2146
2147 #[test]
2148 fn reject_truncated_attribute_value() {
2149 let buf = [0x40, 0x01, 0x01];
2151 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2152 }
2153
2154 #[test]
2155 fn reject_bad_origin_length() {
2156 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2158 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2159 }
2160
2161 #[test]
2162 fn as_path_with_set_and_sequence() {
2163 let attrs = vec![PathAttribute::AsPath(AsPath {
2165 segments: vec![
2166 AsPathSegment::AsSequence(vec![65001]),
2167 AsPathSegment::AsSet(vec![65002, 65003]),
2168 ],
2169 })];
2170
2171 let mut buf = Vec::new();
2172 encode_path_attributes(&attrs, &mut buf, true, false);
2173 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2174 assert_eq!(decoded, attrs);
2175 }
2176
2177 #[test]
2178 fn decode_communities_single() {
2179 let community: u32 = (65001 << 16) | 0x0064;
2182 let bytes = community.to_be_bytes();
2183 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2184 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2185 assert_eq!(attrs.len(), 1);
2186 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2187 }
2188
2189 #[test]
2190 fn decode_communities_multiple() {
2191 let c1: u32 = (65001 << 16) | 0x0064;
2192 let c2: u32 = (65002 << 16) | 0x00C8;
2193 let b1 = c1.to_be_bytes();
2194 let b2 = c2.to_be_bytes();
2195 let buf = [
2196 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2197 ];
2198 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2199 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2200 }
2201
2202 #[test]
2203 fn decode_communities_empty() {
2204 let buf = [0xC0, 0x08, 0x00];
2206 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2207 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2208 }
2209
2210 #[test]
2211 fn decode_communities_odd_length_rejected() {
2212 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2214 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2215 }
2216
2217 #[test]
2218 fn communities_roundtrip() {
2219 let c1: u32 = (65001 << 16) | 0x0064;
2220 let c2: u32 = (65002 << 16) | 0x00C8;
2221 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2222
2223 let mut buf = Vec::new();
2224 encode_path_attributes(&attrs, &mut buf, true, false);
2225 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2226 assert_eq!(decoded, attrs);
2227 }
2228
2229 #[test]
2230 fn communities_type_code_and_flags() {
2231 let attr = PathAttribute::Communities(vec![]);
2232 assert_eq!(attr.type_code(), 8);
2233 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2234 }
2235
2236 #[test]
2239 fn decode_extended_communities_single() {
2240 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2242 let bytes = ec.as_u64().to_be_bytes();
2243 let buf = [
2244 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2245 bytes[7],
2246 ];
2247 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2248 assert_eq!(attrs.len(), 1);
2249 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2250 }
2251
2252 #[test]
2253 fn decode_extended_communities_multiple() {
2254 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2255 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2256 let b1 = ec1.as_u64().to_be_bytes();
2257 let b2 = ec2.as_u64().to_be_bytes();
2258 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2260 buf.extend_from_slice(&b2);
2261 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2262 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2263 }
2264
2265 #[test]
2266 fn decode_extended_communities_empty() {
2267 let buf = [0xC0, 0x10, 0x00];
2268 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2269 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2270 }
2271
2272 #[test]
2273 fn decode_extended_communities_bad_length() {
2274 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2276 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2277 }
2278
2279 #[test]
2280 fn extended_communities_roundtrip() {
2281 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2282 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2283 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2284
2285 let mut buf = Vec::new();
2286 encode_path_attributes(&attrs, &mut buf, true, false);
2287 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2288 assert_eq!(decoded, attrs);
2289 }
2290
2291 #[test]
2292 fn extended_communities_type_code_and_flags() {
2293 let attr = PathAttribute::ExtendedCommunities(vec![]);
2294 assert_eq!(attr.type_code(), 16);
2295 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2296 }
2297
2298 #[test]
2299 fn extended_community_type_subtype() {
2300 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2302 assert_eq!(ec.type_byte(), 0x00);
2303 assert_eq!(ec.subtype(), 0x02);
2304 assert!(ec.is_transitive());
2305 }
2306
2307 #[test]
2308 fn extended_community_route_target() {
2309 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2311 assert_eq!(ec.route_target(), Some((65001, 100)));
2312 assert_eq!(ec.route_origin(), None);
2313
2314 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2316 assert_eq!(ec4.route_target(), Some((65537, 200)));
2317
2318 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2321 let (g, l) = ec_ipv4.route_target().unwrap();
2322 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2324 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2326 }
2327
2328 #[test]
2329 fn extended_community_is_transitive() {
2330 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2332 assert!(t.is_transitive());
2333
2334 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2336 assert!(!nt.is_transitive());
2337 }
2338
2339 #[test]
2340 fn extended_community_display() {
2341 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2342 assert_eq!(rt.to_string(), "RT:65001:100");
2343
2344 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2345 assert_eq!(ro.to_string(), "RO:65001:100");
2346
2347 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2349 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2350
2351 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2353 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2354
2355 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2357 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2358
2359 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2361 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2362 }
2363
2364 #[test]
2365 fn unknown_attribute_roundtrip() {
2366 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2369 flags: 0xC0,
2370 type_code: 99,
2371 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2372 })];
2373
2374 let mut buf = Vec::new();
2375 encode_path_attributes(&attrs, &mut buf, true, false);
2376 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2377 assert_eq!(
2378 decoded,
2379 vec![PathAttribute::Unknown(RawAttribute {
2380 flags: 0xE0, type_code: 99,
2382 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2383 })]
2384 );
2385 }
2386
2387 #[test]
2388 fn origin_with_optional_flag_rejected() {
2389 let buf = [0xC0, 0x01, 0x01, 0x00];
2391 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2392 match &err {
2393 DecodeError::UpdateAttributeError { subcode, .. } => {
2394 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2395 }
2396 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2397 }
2398 }
2399
2400 #[test]
2401 fn med_with_transitive_flag_rejected() {
2402 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2404 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2405 match &err {
2406 DecodeError::UpdateAttributeError { subcode, .. } => {
2407 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2408 }
2409 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2410 }
2411 }
2412
2413 #[test]
2414 fn communities_without_optional_rejected() {
2415 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2417 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2418 match &err {
2419 DecodeError::UpdateAttributeError { subcode, .. } => {
2420 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2421 }
2422 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2423 }
2424 }
2425
2426 #[test]
2427 fn next_hop_length_error_subcode() {
2428 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2430 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2431 match &err {
2432 DecodeError::UpdateAttributeError { subcode, .. } => {
2433 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2434 }
2435 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2436 }
2437 }
2438
2439 #[test]
2440 fn invalid_origin_value_subcode() {
2441 let buf = [0x40, 0x01, 0x01, 0x05];
2443 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2444 match &err {
2445 DecodeError::UpdateAttributeError { subcode, .. } => {
2446 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2447 }
2448 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2449 }
2450 }
2451
2452 #[test]
2453 fn as_path_bad_segment_subcode() {
2454 let buf = [
2456 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2460 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2461 match &err {
2462 DecodeError::UpdateAttributeError { subcode, .. } => {
2463 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2464 }
2465 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2466 }
2467 }
2468
2469 #[test]
2470 fn encode_unknown_transitive_sets_partial() {
2471 let attr = PathAttribute::Unknown(RawAttribute {
2472 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2474 data: Bytes::from_static(&[1, 2]),
2475 });
2476 let mut buf = Vec::new();
2477 encode_path_attributes(&[attr], &mut buf, true, false);
2478 assert_eq!(
2480 buf[0],
2481 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2482 );
2483 }
2484
2485 #[test]
2486 fn encode_unknown_wellknown_transitive_no_partial() {
2487 let attr = PathAttribute::Unknown(RawAttribute {
2489 flags: attr_flags::TRANSITIVE, type_code: 99,
2491 data: Bytes::from_static(&[1, 2]),
2492 });
2493 let mut buf = Vec::new();
2494 encode_path_attributes(&[attr], &mut buf, true, false);
2495 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2496 }
2497
2498 #[test]
2499 fn encode_unknown_nontransitive_no_partial() {
2500 let attr = PathAttribute::Unknown(RawAttribute {
2501 flags: attr_flags::OPTIONAL, type_code: 99,
2503 data: Bytes::from_static(&[1, 2]),
2504 });
2505 let mut buf = Vec::new();
2506 encode_path_attributes(&[attr], &mut buf, true, false);
2507 assert_eq!(buf[0], attr_flags::OPTIONAL);
2509 }
2510
2511 fn nlri(prefix: Prefix) -> NlriEntry {
2515 NlriEntry { path_id: 0, prefix }
2516 }
2517
2518 #[test]
2519 fn mp_reach_nlri_ipv6_roundtrip() {
2520 use crate::capability::{Afi, Safi};
2521 use crate::nlri::{Ipv6Prefix, Prefix};
2522
2523 let mp = MpReachNlri {
2524 afi: Afi::Ipv6,
2525 safi: Safi::Unicast,
2526 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2527 link_local_next_hop: None,
2528 announced: vec![
2529 nlri(Prefix::V6(Ipv6Prefix::new(
2530 "2001:db8:1::".parse().unwrap(),
2531 48,
2532 ))),
2533 nlri(Prefix::V6(Ipv6Prefix::new(
2534 "2001:db8:2::".parse().unwrap(),
2535 48,
2536 ))),
2537 ],
2538 flowspec_announced: vec![],
2539 evpn_announced: vec![],
2540 };
2541 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2542
2543 let mut buf = Vec::new();
2544 encode_path_attributes(&attrs, &mut buf, true, false);
2545 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2546 assert_eq!(decoded.len(), 1);
2547 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2548 }
2549
2550 #[test]
2551 fn mp_unreach_nlri_ipv6_roundtrip() {
2552 use crate::capability::{Afi, Safi};
2553 use crate::nlri::{Ipv6Prefix, Prefix};
2554
2555 let mp = MpUnreachNlri {
2556 afi: Afi::Ipv6,
2557 safi: Safi::Unicast,
2558 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2559 "2001:db8:1::".parse().unwrap(),
2560 48,
2561 )))],
2562 flowspec_withdrawn: vec![],
2563 evpn_withdrawn: vec![],
2564 };
2565 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2566
2567 let mut buf = Vec::new();
2568 encode_path_attributes(&attrs, &mut buf, true, false);
2569 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2570 assert_eq!(decoded.len(), 1);
2571 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2572 }
2573
2574 #[test]
2575 fn mp_reach_nlri_ipv4_roundtrip() {
2576 use crate::capability::{Afi, Safi};
2577 use crate::nlri::Prefix;
2578
2579 let mp = MpReachNlri {
2580 afi: Afi::Ipv4,
2581 safi: Safi::Unicast,
2582 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2583 link_local_next_hop: None,
2584 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2585 Ipv4Addr::new(10, 1, 0, 0),
2586 16,
2587 )))],
2588 flowspec_announced: vec![],
2589 evpn_announced: vec![],
2590 };
2591 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2592
2593 let mut buf = Vec::new();
2594 encode_path_attributes(&attrs, &mut buf, true, false);
2595 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2596 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2597 }
2598
2599 #[test]
2600 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2601 use crate::capability::{Afi, Safi};
2602 use crate::nlri::Prefix;
2603
2604 let mp = MpReachNlri {
2605 afi: Afi::Ipv4,
2606 safi: Safi::Unicast,
2607 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2608 link_local_next_hop: None,
2609 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2610 Ipv4Addr::new(10, 1, 0, 0),
2611 16,
2612 )))],
2613 flowspec_announced: vec![],
2614 evpn_announced: vec![],
2615 };
2616 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2617
2618 let mut buf = Vec::new();
2619 encode_path_attributes(&attrs, &mut buf, true, false);
2620 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2621 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2622 }
2623
2624 #[test]
2625 fn mp_reach_nlri_type_code_and_flags() {
2626 use crate::capability::{Afi, Safi};
2627
2628 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2629 afi: Afi::Ipv6,
2630 safi: Safi::Unicast,
2631 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2632 link_local_next_hop: None,
2633 announced: vec![],
2634 flowspec_announced: vec![],
2635 evpn_announced: vec![],
2636 });
2637 assert_eq!(attr.type_code(), 14);
2638 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2640 }
2641
2642 #[test]
2643 fn mp_unreach_nlri_type_code_and_flags() {
2644 use crate::capability::{Afi, Safi};
2645
2646 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2647 afi: Afi::Ipv6,
2648 safi: Safi::Unicast,
2649 withdrawn: vec![],
2650 flowspec_withdrawn: vec![],
2651 evpn_withdrawn: vec![],
2652 });
2653 assert_eq!(attr.type_code(), 15);
2654 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2655 }
2656
2657 #[test]
2658 fn mp_reach_nlri_empty_nlri() {
2659 use crate::capability::{Afi, Safi};
2660
2661 let mp = MpReachNlri {
2662 afi: Afi::Ipv6,
2663 safi: Safi::Unicast,
2664 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2665 link_local_next_hop: None,
2666 announced: vec![],
2667 flowspec_announced: vec![],
2668 evpn_announced: vec![],
2669 };
2670 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2671
2672 let mut buf = Vec::new();
2673 encode_path_attributes(&attrs, &mut buf, true, false);
2674 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2675 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2676 }
2677
2678 #[test]
2679 fn mp_reach_nlri_bad_flags_rejected() {
2680 let mut value = Vec::new();
2684 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();
2691 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2694 buf.push(value.len() as u8);
2695 buf.extend_from_slice(&value);
2696
2697 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2698 assert!(matches!(
2699 err,
2700 DecodeError::UpdateAttributeError {
2701 subcode: 4, ..
2703 }
2704 ));
2705 }
2706
2707 #[test]
2710 #[expect(clippy::cast_possible_truncation)]
2711 fn mp_reach_nlri_ipv4_addpath_decode() {
2712 use crate::capability::{Afi, Safi};
2713 use crate::nlri::Prefix;
2714
2715 let mut value = Vec::new();
2718 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());
2725 value.push(16);
2726 value.extend_from_slice(&[10, 1]);
2727
2728 let mut buf = Vec::new();
2729 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2732 buf.extend_from_slice(&value);
2733
2734 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2736 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2737 panic!("expected MpReachNlri");
2738 };
2739 assert_eq!(mp.announced.len(), 1);
2740 assert_eq!(mp.announced[0].path_id, 42);
2741 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2742
2743 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2746 }
2747
2748 #[test]
2749 #[expect(clippy::cast_possible_truncation)]
2750 fn mp_reach_nlri_ipv6_addpath_decode() {
2751 use crate::capability::{Afi, Safi};
2752 use crate::nlri::{Ipv6Prefix, Prefix};
2753
2754 let mut value = Vec::new();
2756 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());
2760 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2763 value.push(48);
2764 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2765
2766 let mut buf = Vec::new();
2767 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2770 buf.extend_from_slice(&value);
2771
2772 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2773 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2774 panic!("expected MpReachNlri");
2775 };
2776 assert_eq!(mp.announced.len(), 1);
2777 assert_eq!(mp.announced[0].path_id, 99);
2778 assert_eq!(
2779 mp.announced[0].prefix,
2780 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2781 );
2782 }
2783
2784 #[test]
2785 #[expect(clippy::cast_possible_truncation)]
2786 fn mp_unreach_nlri_ipv6_addpath_decode() {
2787 use crate::capability::{Afi, Safi};
2788 use crate::nlri::{Ipv6Prefix, Prefix};
2789
2790 let mut value = Vec::new();
2792 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2796 value.push(48);
2797 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2798
2799 let mut buf = Vec::new();
2800 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2803 buf.extend_from_slice(&value);
2804
2805 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2806 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2807 panic!("expected MpUnreachNlri");
2808 };
2809 assert_eq!(mp.withdrawn.len(), 1);
2810 assert_eq!(mp.withdrawn[0].path_id, 7);
2811 assert_eq!(
2812 mp.withdrawn[0].prefix,
2813 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2814 );
2815 }
2816
2817 #[test]
2818 fn mp_reach_addpath_only_applies_to_matching_family() {
2819 use crate::capability::{Afi, Safi};
2820 use crate::nlri::{Ipv6Prefix, Prefix};
2821
2822 let mp = MpReachNlri {
2824 afi: Afi::Ipv6,
2825 safi: Safi::Unicast,
2826 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2827 link_local_next_hop: None,
2828 announced: vec![NlriEntry {
2829 path_id: 0,
2830 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2831 }],
2832 flowspec_announced: vec![],
2833 evpn_announced: vec![],
2834 };
2835 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2836
2837 let mut buf = Vec::new();
2838 encode_path_attributes(&attrs, &mut buf, true, false);
2839
2840 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2842 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2843 }
2844
2845 #[test]
2848 fn decode_originator_id() {
2849 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2851 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2852 assert_eq!(
2853 attrs[0],
2854 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2855 );
2856 }
2857
2858 #[test]
2863 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
2864 use crate::capability::{Afi, Safi};
2865 use crate::nlri::{Ipv6Prefix, Prefix};
2866 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
2867 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
2868 let mp = MpReachNlri {
2869 afi: Afi::Ipv6,
2870 safi: Safi::Unicast,
2871 next_hop: IpAddr::V6(global),
2872 link_local_next_hop: Some(link_local),
2873 announced: vec![NlriEntry {
2874 path_id: 0,
2875 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2876 }],
2877 flowspec_announced: vec![],
2878 evpn_announced: vec![],
2879 };
2880 let attr = PathAttribute::MpReachNlri(mp.clone());
2881 let mut buf = Vec::new();
2882 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2883
2884 let extended = (buf[0] & 0x10) != 0;
2888 let value_off = if extended { 4 } else { 3 };
2889 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
2891 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
2892 assert_eq!(
2893 &buf[value_off + 20..value_off + 36],
2894 &link_local.octets(),
2895 "encoded link-local bytes must match the input"
2896 );
2897
2898 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2899 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
2900 panic!("expected MpReachNlri");
2901 };
2902 assert_eq!(dec.next_hop, IpAddr::V6(global));
2903 assert_eq!(dec.link_local_next_hop, Some(link_local));
2904 }
2905
2906 #[test]
2914 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
2915 let value: &[u8] = &[
2918 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
2925 ];
2926 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
2929 attr.extend_from_slice(value);
2930 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
2931 match err {
2932 DecodeError::MalformedField { detail, .. } => {
2933 assert!(
2934 detail.contains("FlowSpec next-hop length"),
2935 "expected FlowSpec NH-Len rejection, got: {detail}"
2936 );
2937 }
2938 other => panic!("expected MalformedField, got {other:?}"),
2939 }
2940 }
2941
2942 #[test]
2943 fn originator_id_roundtrip() {
2944 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2945 let mut buf = Vec::new();
2946 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2947 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2948 assert_eq!(decoded, vec![attr]);
2949 }
2950
2951 #[test]
2952 fn originator_id_wrong_length() {
2953 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2955 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2956 assert!(matches!(
2957 err,
2958 DecodeError::UpdateAttributeError {
2959 subcode: 5, ..
2961 }
2962 ));
2963 }
2964
2965 #[test]
2966 fn originator_id_wrong_flags() {
2967 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2969 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2970 assert!(matches!(
2971 err,
2972 DecodeError::UpdateAttributeError {
2973 subcode: 4, ..
2975 }
2976 ));
2977 }
2978
2979 #[test]
2982 fn decode_cluster_list() {
2983 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2985 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2986 assert_eq!(
2987 attrs[0],
2988 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2989 );
2990 }
2991
2992 #[test]
2993 fn cluster_list_roundtrip() {
2994 let attr = PathAttribute::ClusterList(vec![
2995 Ipv4Addr::new(10, 0, 0, 1),
2996 Ipv4Addr::new(10, 0, 0, 2),
2997 ]);
2998 let mut buf = Vec::new();
2999 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3000 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3001 assert_eq!(decoded, vec![attr]);
3002 }
3003
3004 #[test]
3005 fn cluster_list_wrong_length() {
3006 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
3008 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3009 assert!(matches!(
3010 err,
3011 DecodeError::UpdateAttributeError {
3012 subcode: 5, ..
3014 }
3015 ));
3016 }
3017
3018 #[test]
3023 fn large_community_display() {
3024 let lc = LargeCommunity::new(65001, 100, 200);
3025 assert_eq!(lc.to_string(), "65001:100:200");
3026 }
3027
3028 #[test]
3029 fn large_community_type_code_and_flags() {
3030 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3031 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3032 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3033 }
3034
3035 #[test]
3036 fn decode_large_community_single() {
3037 let mut buf = vec![0xC0, 32, 12];
3039 buf.extend_from_slice(&65001u32.to_be_bytes());
3040 buf.extend_from_slice(&100u32.to_be_bytes());
3041 buf.extend_from_slice(&200u32.to_be_bytes());
3042 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3043 assert_eq!(attrs.len(), 1);
3044 assert_eq!(
3045 attrs[0],
3046 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3047 );
3048 }
3049
3050 #[test]
3051 fn decode_large_community_multiple() {
3052 let mut buf = vec![0xC0, 32, 24];
3054 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3055 buf.extend_from_slice(&g.to_be_bytes());
3056 buf.extend_from_slice(&l1.to_be_bytes());
3057 buf.extend_from_slice(&l2.to_be_bytes());
3058 }
3059 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3060 assert_eq!(
3061 attrs[0],
3062 PathAttribute::LargeCommunities(vec![
3063 LargeCommunity::new(65001, 100, 200),
3064 LargeCommunity::new(65002, 300, 400),
3065 ])
3066 );
3067 }
3068
3069 #[test]
3070 fn decode_large_community_bad_length() {
3071 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3073 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3074 assert!(matches!(
3075 err,
3076 DecodeError::UpdateAttributeError {
3077 subcode: 5, ..
3079 }
3080 ));
3081 }
3082
3083 #[test]
3084 fn decode_large_community_empty_rejected() {
3085 let buf = [0xC0, 32, 0];
3087 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3088 assert!(matches!(
3089 err,
3090 DecodeError::UpdateAttributeError {
3091 subcode: 5, ..
3093 }
3094 ));
3095 }
3096
3097 #[test]
3098 fn large_community_roundtrip() {
3099 let lcs = vec![
3100 LargeCommunity::new(65001, 100, 200),
3101 LargeCommunity::new(0, u32::MAX, 42),
3102 ];
3103 let attr = PathAttribute::LargeCommunities(lcs.clone());
3104 let mut buf = Vec::new();
3105 encode_path_attributes(&[attr], &mut buf, true, false);
3106 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3107 assert_eq!(decoded.len(), 1);
3108 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3109 }
3110
3111 #[test]
3112 fn large_community_expected_flags_validated() {
3113 let mut buf = vec![0x40, 32, 12];
3115 buf.extend_from_slice(&1u32.to_be_bytes());
3116 buf.extend_from_slice(&2u32.to_be_bytes());
3117 buf.extend_from_slice(&3u32.to_be_bytes());
3118 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3119 assert!(matches!(
3120 err,
3121 DecodeError::UpdateAttributeError {
3122 subcode: 4, ..
3124 }
3125 ));
3126 }
3127
3128 #[test]
3133 fn aspath_string_sequence() {
3134 let p = AsPath {
3135 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3136 };
3137 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3138 }
3139
3140 #[test]
3141 fn aspath_string_set() {
3142 let p = AsPath {
3143 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3144 };
3145 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3146 }
3147
3148 #[test]
3149 fn aspath_string_mixed() {
3150 let p = AsPath {
3151 segments: vec![
3152 AsPathSegment::AsSequence(vec![65001, 65002]),
3153 AsPathSegment::AsSet(vec![65003, 65004]),
3154 ],
3155 };
3156 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3157 }
3158
3159 #[test]
3160 fn aspath_string_empty() {
3161 let p = AsPath { segments: vec![] };
3162 assert_eq!(p.to_aspath_string(), "");
3163 }
3164
3165 #[test]
3170 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3171 let bytes = vec![
3174 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3180 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3181 match err {
3182 DecodeError::MalformedField { detail, .. } => {
3183 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3184 }
3185 other => panic!("expected MalformedField, got {other:?}"),
3186 }
3187 }
3188
3189 #[test]
3190 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3191 let bytes = vec![
3192 0x00, 0x02, 70, 3, 0, ];
3196 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3197 match err {
3198 DecodeError::MalformedField { detail, .. } => {
3199 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3200 }
3201 other => panic!("expected MalformedField, got {other:?}"),
3202 }
3203 }
3204
3205 #[test]
3206 fn pmsi_tunnel_path_attribute_round_trips_through_dispatch() {
3207 let pmsi =
3212 crate::pmsi::PmsiTunnel::for_evpn_ingress_replication(100, "10.0.0.1".parse().unwrap());
3213 let attrs = vec![
3214 PathAttribute::Origin(Origin::Igp),
3215 PathAttribute::AsPath(AsPath { segments: vec![] }),
3216 PathAttribute::LocalPref(100),
3217 PathAttribute::PmsiTunnel(pmsi.clone()),
3218 ];
3219
3220 let mut buf = Vec::new();
3221 encode_path_attributes(&attrs, &mut buf, true, false);
3222 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3223
3224 assert_eq!(decoded, attrs);
3225
3226 let pmsi_decoded = decoded
3229 .iter()
3230 .find_map(|a| match a {
3231 PathAttribute::PmsiTunnel(p) => Some(p),
3232 _ => None,
3233 })
3234 .expect("PMSI present");
3235 assert_eq!(pmsi_decoded, &pmsi);
3236 assert_eq!(
3237 PathAttribute::PmsiTunnel(pmsi).flags(),
3238 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3239 );
3240 }
3241
3242 #[test]
3243 fn pmsi_tunnel_decode_attribute_with_truncated_value_is_malformed() {
3244 let buf = [
3246 0xC0, 22, 0x04, 0x00, 0x06, 0x00, 0x00,
3250 ];
3251 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3252 assert!(matches!(err, DecodeError::MalformedField { .. }));
3253 }
3254}