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 Unknown(RawAttribute),
559}
560
561impl PathAttribute {
562 #[must_use]
564 pub fn type_code(&self) -> u8 {
565 match self {
566 Self::Origin(_) => attr_type::ORIGIN,
567 Self::AsPath(_) => attr_type::AS_PATH,
568 Self::NextHop(_) => attr_type::NEXT_HOP,
569 Self::LocalPref(_) => attr_type::LOCAL_PREF,
570 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
571 Self::Communities(_) => attr_type::COMMUNITIES,
572 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
573 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
574 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
575 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
576 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
577 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
578 Self::Unknown(raw) => raw.type_code,
579 }
580 }
581
582 #[must_use]
584 pub fn flags(&self) -> u8 {
585 match self {
586 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
587 attr_flags::TRANSITIVE
588 }
589 Self::Med(_)
590 | Self::OriginatorId(_)
591 | Self::ClusterList(_)
592 | Self::MpReachNlri(_)
593 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
594 Self::Communities(_) | Self::ExtendedCommunities(_) | Self::LargeCommunities(_) => {
595 attr_flags::OPTIONAL | attr_flags::TRANSITIVE
596 }
597 Self::Unknown(raw) => raw.flags,
598 }
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Eq, Hash)]
607pub struct RawAttribute {
608 pub flags: u8,
610 pub type_code: u8,
612 pub data: Bytes,
614}
615
616pub fn decode_path_attributes(
627 mut buf: &[u8],
628 four_octet_as: bool,
629 add_path_families: &[(Afi, Safi)],
630) -> Result<Vec<PathAttribute>, DecodeError> {
631 let mut attrs = Vec::new();
632
633 while !buf.is_empty() {
634 if buf.len() < 2 {
636 return Err(DecodeError::MalformedField {
637 message_type: "UPDATE",
638 detail: "truncated attribute header".to_string(),
639 });
640 }
641
642 let flags = buf[0];
643 let type_code = buf[1];
644 buf = &buf[2..];
645
646 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
647 let value_len = if extended {
648 if buf.len() < 2 {
649 return Err(DecodeError::MalformedField {
650 message_type: "UPDATE",
651 detail: "truncated extended-length attribute".to_string(),
652 });
653 }
654 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
655 buf = &buf[2..];
656 len
657 } else {
658 if buf.is_empty() {
659 return Err(DecodeError::MalformedField {
660 message_type: "UPDATE",
661 detail: "truncated attribute length".to_string(),
662 });
663 }
664 let len = buf[0] as usize;
665 buf = &buf[1..];
666 len
667 };
668
669 if buf.len() < value_len {
670 return Err(DecodeError::MalformedField {
671 message_type: "UPDATE",
672 detail: format!(
673 "attribute type {type_code} value truncated: need {value_len}, have {}",
674 buf.len()
675 ),
676 });
677 }
678
679 let value = &buf[..value_len];
680 buf = &buf[value_len..];
681
682 let attr =
683 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
684 attrs.push(attr);
685 }
686
687 Ok(attrs)
688}
689
690#[expect(clippy::too_many_lines)]
692fn decode_attribute_value(
693 flags: u8,
694 type_code: u8,
695 value: &[u8],
696 four_octet_as: bool,
697 add_path_families: &[(Afi, Safi)],
698) -> Result<PathAttribute, DecodeError> {
699 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
701 if let Some(expected) = expected_flags(type_code)
702 && (flags & flags_mask) != expected
703 {
704 return Err(DecodeError::UpdateAttributeError {
705 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
706 data: attr_error_data(flags, type_code, value),
707 detail: format!(
708 "type {} flags {:#04x} (expected {:#04x})",
709 type_code,
710 flags & flags_mask,
711 expected
712 ),
713 });
714 }
715
716 match type_code {
717 attr_type::ORIGIN => {
718 if value.len() != 1 {
719 return Err(DecodeError::UpdateAttributeError {
720 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
721 data: attr_error_data(flags, type_code, value),
722 detail: format!("ORIGIN length {} (expected 1)", value.len()),
723 });
724 }
725 match Origin::from_u8(value[0]) {
726 Some(origin) => Ok(PathAttribute::Origin(origin)),
727 None => Err(DecodeError::UpdateAttributeError {
728 subcode: update_subcode::INVALID_ORIGIN,
729 data: attr_error_data(flags, type_code, value),
730 detail: format!("invalid ORIGIN value {}", value[0]),
731 }),
732 }
733 }
734
735 attr_type::AS_PATH => {
736 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
737 DecodeError::UpdateAttributeError {
738 subcode: update_subcode::MALFORMED_AS_PATH,
739 data: attr_error_data(flags, type_code, value),
740 detail: e.to_string(),
741 }
742 })?;
743 Ok(PathAttribute::AsPath(AsPath { segments }))
744 }
745
746 attr_type::NEXT_HOP => {
747 if value.len() != 4 {
748 return Err(DecodeError::UpdateAttributeError {
749 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
750 data: attr_error_data(flags, type_code, value),
751 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
752 });
753 }
754 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
755 Ok(PathAttribute::NextHop(addr))
756 }
757
758 attr_type::MULTI_EXIT_DISC => {
759 if value.len() != 4 {
760 return Err(DecodeError::UpdateAttributeError {
761 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
762 data: attr_error_data(flags, type_code, value),
763 detail: format!("MED length {} (expected 4)", value.len()),
764 });
765 }
766 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
767 Ok(PathAttribute::Med(med))
768 }
769
770 attr_type::LOCAL_PREF => {
771 if value.len() != 4 {
772 return Err(DecodeError::UpdateAttributeError {
773 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
774 data: attr_error_data(flags, type_code, value),
775 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
776 });
777 }
778 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
779 Ok(PathAttribute::LocalPref(lp))
780 }
781
782 attr_type::COMMUNITIES => {
783 if !value.len().is_multiple_of(4) {
784 return Err(DecodeError::UpdateAttributeError {
785 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
786 data: attr_error_data(flags, type_code, value),
787 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
788 });
789 }
790 let communities = value
791 .chunks_exact(4)
792 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
793 .collect();
794 Ok(PathAttribute::Communities(communities))
795 }
796
797 attr_type::EXTENDED_COMMUNITIES => {
798 if !value.len().is_multiple_of(8) {
799 return Err(DecodeError::UpdateAttributeError {
800 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
801 data: attr_error_data(flags, type_code, value),
802 detail: format!(
803 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
804 value.len()
805 ),
806 });
807 }
808 let communities = value
809 .chunks_exact(8)
810 .map(|c| {
811 ExtendedCommunity::new(u64::from_be_bytes([
812 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
813 ]))
814 })
815 .collect();
816 Ok(PathAttribute::ExtendedCommunities(communities))
817 }
818
819 attr_type::ORIGINATOR_ID => {
820 if value.len() != 4 {
821 return Err(DecodeError::UpdateAttributeError {
822 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
823 data: attr_error_data(flags, type_code, value),
824 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
825 });
826 }
827 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
828 Ok(PathAttribute::OriginatorId(addr))
829 }
830
831 attr_type::CLUSTER_LIST => {
832 if !value.len().is_multiple_of(4) {
833 return Err(DecodeError::UpdateAttributeError {
834 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
835 data: attr_error_data(flags, type_code, value),
836 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
837 });
838 }
839 let ids = value
840 .chunks_exact(4)
841 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
842 .collect();
843 Ok(PathAttribute::ClusterList(ids))
844 }
845
846 attr_type::LARGE_COMMUNITIES => {
847 if value.is_empty() || !value.len().is_multiple_of(12) {
848 return Err(DecodeError::UpdateAttributeError {
849 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
850 data: attr_error_data(flags, type_code, value),
851 detail: format!(
852 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
853 value.len()
854 ),
855 });
856 }
857 let communities = value
858 .chunks_exact(12)
859 .map(|c| {
860 LargeCommunity::new(
861 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
862 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
863 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
864 )
865 })
866 .collect();
867 Ok(PathAttribute::LargeCommunities(communities))
868 }
869
870 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
871 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
872
873 _ => Ok(PathAttribute::Unknown(RawAttribute {
875 flags,
876 type_code,
877 data: Bytes::copy_from_slice(value),
878 })),
879 }
880}
881
882#[expect(clippy::too_many_lines)]
887fn decode_mp_reach_nlri(
888 value: &[u8],
889 add_path_families: &[(Afi, Safi)],
890) -> Result<PathAttribute, DecodeError> {
891 if value.len() < 5 {
892 return Err(DecodeError::MalformedField {
893 message_type: "UPDATE",
894 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
895 });
896 }
897
898 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
899 let safi_raw = value[2];
900 let nh_len = value[3] as usize;
901
902 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
903 message_type: "UPDATE",
904 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
905 })?;
906 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
907 message_type: "UPDATE",
908 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
909 })?;
910
911 if value.len() < 4 + nh_len + 1 {
913 return Err(DecodeError::MalformedField {
914 message_type: "UPDATE",
915 detail: format!(
916 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
917 value.len()
918 ),
919 });
920 }
921
922 let nh_bytes = &value[4..4 + nh_len];
923 let mut link_local_next_hop: Option<Ipv6Addr> = None;
925 let next_hop = if safi == Safi::FlowSpec {
926 if nh_len != 0 {
927 return Err(DecodeError::MalformedField {
928 message_type: "UPDATE",
929 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
930 });
931 }
932 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
933 } else {
934 match afi {
935 Afi::Ipv4 => match nh_len {
936 4 => IpAddr::V4(Ipv4Addr::new(
937 nh_bytes[0],
938 nh_bytes[1],
939 nh_bytes[2],
940 nh_bytes[3],
941 )),
942 16 | 32 => {
943 let mut octets = [0u8; 16];
944 octets.copy_from_slice(&nh_bytes[..16]);
945 if nh_len == 32 {
946 let mut ll = [0u8; 16];
947 ll.copy_from_slice(&nh_bytes[16..32]);
948 link_local_next_hop = Some(Ipv6Addr::from(ll));
949 }
950 IpAddr::V6(Ipv6Addr::from(octets))
951 }
952 _ => {
953 return Err(DecodeError::MalformedField {
954 message_type: "UPDATE",
955 detail: format!(
956 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
957 ),
958 });
959 }
960 },
961 Afi::Ipv6 => {
962 if nh_len != 16 && nh_len != 32 {
963 return Err(DecodeError::MalformedField {
964 message_type: "UPDATE",
965 detail: format!(
966 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
967 ),
968 });
969 }
970 let mut octets = [0u8; 16];
971 octets.copy_from_slice(&nh_bytes[..16]);
972 if nh_len == 32 {
973 let mut ll = [0u8; 16];
974 ll.copy_from_slice(&nh_bytes[16..32]);
975 link_local_next_hop = Some(Ipv6Addr::from(ll));
976 }
977 IpAddr::V6(Ipv6Addr::from(octets))
978 }
979 Afi::L2Vpn => match nh_len {
980 4 => IpAddr::V4(Ipv4Addr::new(
981 nh_bytes[0],
982 nh_bytes[1],
983 nh_bytes[2],
984 nh_bytes[3],
985 )),
986 16 => {
987 let mut octets = [0u8; 16];
988 octets.copy_from_slice(&nh_bytes[..16]);
989 IpAddr::V6(Ipv6Addr::from(octets))
990 }
991 _ => {
992 return Err(DecodeError::MalformedField {
993 message_type: "UPDATE",
994 detail: format!(
995 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
996 ),
997 });
998 }
999 },
1000 }
1001 };
1002
1003 let nlri_start = 4 + nh_len + 1;
1005 let nlri_bytes = &value[nlri_start..];
1006
1007 if safi == Safi::FlowSpec {
1009 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1010 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1011 afi,
1012 safi,
1013 next_hop,
1014 link_local_next_hop,
1015 announced: vec![],
1016 flowspec_announced: flowspec_rules,
1017 evpn_announced: vec![],
1018 }));
1019 }
1020
1021 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1023 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1024 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1025 afi,
1026 safi,
1027 next_hop,
1028 link_local_next_hop,
1029 announced: vec![],
1030 flowspec_announced: vec![],
1031 evpn_announced: routes,
1032 }));
1033 }
1034
1035 if safi == Safi::Evpn {
1039 return Err(DecodeError::MalformedField {
1040 message_type: "UPDATE",
1041 detail: format!(
1042 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1043 afi as u16
1044 ),
1045 });
1046 }
1047
1048 let add_path = add_path_families.contains(&(afi, safi));
1049 let announced = match (afi, add_path) {
1050 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1051 .into_iter()
1052 .map(|p| NlriEntry {
1053 path_id: 0,
1054 prefix: Prefix::V4(p),
1055 })
1056 .collect(),
1057 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1058 .into_iter()
1059 .map(|e| NlriEntry {
1060 path_id: e.path_id,
1061 prefix: Prefix::V4(e.prefix),
1062 })
1063 .collect(),
1064 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1065 .into_iter()
1066 .map(|p| NlriEntry {
1067 path_id: 0,
1068 prefix: Prefix::V6(p),
1069 })
1070 .collect(),
1071 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1072 (Afi::L2Vpn, _) => {
1073 return Err(DecodeError::MalformedField {
1074 message_type: "UPDATE",
1075 detail: format!(
1076 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1077 safi as u8
1078 ),
1079 });
1080 }
1081 };
1082
1083 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1084 afi,
1085 safi,
1086 next_hop,
1087 link_local_next_hop,
1088 announced,
1089 flowspec_announced: vec![],
1090 evpn_announced: vec![],
1091 }))
1092}
1093
1094fn decode_mp_unreach_nlri(
1099 value: &[u8],
1100 add_path_families: &[(Afi, Safi)],
1101) -> Result<PathAttribute, DecodeError> {
1102 if value.len() < 3 {
1103 return Err(DecodeError::MalformedField {
1104 message_type: "UPDATE",
1105 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1106 });
1107 }
1108
1109 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1110 let safi_raw = value[2];
1111
1112 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1113 message_type: "UPDATE",
1114 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1115 })?;
1116 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1117 message_type: "UPDATE",
1118 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1119 })?;
1120
1121 let withdrawn_bytes = &value[3..];
1122
1123 if safi == Safi::FlowSpec {
1125 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1126 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1127 afi,
1128 safi,
1129 withdrawn: vec![],
1130 flowspec_withdrawn: flowspec_rules,
1131 evpn_withdrawn: vec![],
1132 }));
1133 }
1134
1135 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1137 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1138 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1139 afi,
1140 safi,
1141 withdrawn: vec![],
1142 flowspec_withdrawn: vec![],
1143 evpn_withdrawn: routes,
1144 }));
1145 }
1146
1147 if safi == Safi::Evpn {
1151 return Err(DecodeError::MalformedField {
1152 message_type: "UPDATE",
1153 detail: format!(
1154 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1155 afi as u16
1156 ),
1157 });
1158 }
1159
1160 let add_path = add_path_families.contains(&(afi, safi));
1161 let withdrawn = match (afi, add_path) {
1162 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1163 .into_iter()
1164 .map(|p| NlriEntry {
1165 path_id: 0,
1166 prefix: Prefix::V4(p),
1167 })
1168 .collect(),
1169 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1170 .into_iter()
1171 .map(|e| NlriEntry {
1172 path_id: e.path_id,
1173 prefix: Prefix::V4(e.prefix),
1174 })
1175 .collect(),
1176 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1177 .into_iter()
1178 .map(|p| NlriEntry {
1179 path_id: 0,
1180 prefix: Prefix::V6(p),
1181 })
1182 .collect(),
1183 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1184 (Afi::L2Vpn, _) => {
1185 return Err(DecodeError::MalformedField {
1186 message_type: "UPDATE",
1187 detail: format!(
1188 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1189 safi as u8
1190 ),
1191 });
1192 }
1193 };
1194
1195 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1196 afi,
1197 safi,
1198 withdrawn,
1199 flowspec_withdrawn: vec![],
1200 evpn_withdrawn: vec![],
1201 }))
1202}
1203
1204fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1206 let as_size: usize = if four_octet_as { 4 } else { 2 };
1207 let mut segments = Vec::new();
1208
1209 while !buf.is_empty() {
1210 if buf.len() < 2 {
1211 return Err(DecodeError::MalformedField {
1212 message_type: "UPDATE",
1213 detail: "truncated AS_PATH segment header".to_string(),
1214 });
1215 }
1216
1217 let seg_type = buf[0];
1218 let seg_count = buf[1] as usize;
1219 buf = &buf[2..];
1220
1221 let needed = seg_count * as_size;
1222 if buf.len() < needed {
1223 return Err(DecodeError::MalformedField {
1224 message_type: "UPDATE",
1225 detail: format!(
1226 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1227 buf.len()
1228 ),
1229 });
1230 }
1231
1232 let mut asns = Vec::with_capacity(seg_count);
1233 for _ in 0..seg_count {
1234 let asn = if four_octet_as {
1235 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1236 buf = &buf[4..];
1237 v
1238 } else {
1239 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1240 buf = &buf[2..];
1241 v
1242 };
1243 asns.push(asn);
1244 }
1245
1246 match seg_type {
1247 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1248 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1249 _ => {
1250 return Err(DecodeError::MalformedField {
1251 message_type: "UPDATE",
1252 detail: format!("unknown AS_PATH segment type {seg_type}"),
1253 });
1254 }
1255 }
1256 }
1257
1258 Ok(segments)
1259}
1260
1261pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1264 let mut buf = Vec::with_capacity(3 + value.len());
1265 if value.len() > 255 {
1266 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1267 buf.push(type_code);
1268 #[expect(clippy::cast_possible_truncation)]
1269 let len = value.len() as u16;
1270 buf.extend_from_slice(&len.to_be_bytes());
1271 } else {
1272 buf.push(flags);
1273 buf.push(type_code);
1274 #[expect(clippy::cast_possible_truncation)]
1275 buf.push(value.len() as u8);
1276 }
1277 buf.extend_from_slice(value);
1278 buf
1279}
1280
1281fn expected_flags(type_code: u8) -> Option<u8> {
1284 match type_code {
1285 attr_type::ORIGIN
1287 | attr_type::AS_PATH
1288 | attr_type::NEXT_HOP
1289 | attr_type::LOCAL_PREF
1290 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1291 attr_type::MULTI_EXIT_DISC
1294 | attr_type::ORIGINATOR_ID
1295 | attr_type::CLUSTER_LIST
1296 | attr_type::MP_REACH_NLRI
1297 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1298 attr_type::AGGREGATOR
1300 | attr_type::COMMUNITIES
1301 | attr_type::EXTENDED_COMMUNITIES
1302 | attr_type::LARGE_COMMUNITIES => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1303 _ => None,
1304 }
1305}
1306
1307pub fn encode_path_attributes(
1315 attrs: &[PathAttribute],
1316 buf: &mut Vec<u8>,
1317 four_octet_as: bool,
1318 add_path_mp: bool,
1319) {
1320 for attr in attrs {
1321 let mut value = Vec::new();
1322 let flags;
1323 let type_code;
1324
1325 match attr {
1326 PathAttribute::Origin(origin) => {
1327 flags = attr_flags::TRANSITIVE;
1328 type_code = attr_type::ORIGIN;
1329 value.push(*origin as u8);
1330 }
1331 PathAttribute::AsPath(as_path) => {
1332 flags = attr_flags::TRANSITIVE;
1333 type_code = attr_type::AS_PATH;
1334 encode_as_path(as_path, &mut value, four_octet_as);
1335 }
1336 PathAttribute::NextHop(addr) => {
1337 flags = attr_flags::TRANSITIVE;
1338 type_code = attr_type::NEXT_HOP;
1339 value.extend_from_slice(&addr.octets());
1340 }
1341 PathAttribute::Med(med) => {
1342 flags = attr_flags::OPTIONAL;
1343 type_code = attr_type::MULTI_EXIT_DISC;
1344 value.extend_from_slice(&med.to_be_bytes());
1345 }
1346 PathAttribute::LocalPref(lp) => {
1347 flags = attr_flags::TRANSITIVE;
1348 type_code = attr_type::LOCAL_PREF;
1349 value.extend_from_slice(&lp.to_be_bytes());
1350 }
1351 PathAttribute::Communities(communities) => {
1352 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1353 type_code = attr_type::COMMUNITIES;
1354 for &c in communities {
1355 value.extend_from_slice(&c.to_be_bytes());
1356 }
1357 }
1358 PathAttribute::ExtendedCommunities(communities) => {
1359 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1360 type_code = attr_type::EXTENDED_COMMUNITIES;
1361 for &c in communities {
1362 value.extend_from_slice(&c.as_u64().to_be_bytes());
1363 }
1364 }
1365 PathAttribute::LargeCommunities(communities) => {
1366 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1367 type_code = attr_type::LARGE_COMMUNITIES;
1368 for &c in communities {
1369 value.extend_from_slice(&c.global_admin.to_be_bytes());
1370 value.extend_from_slice(&c.local_data1.to_be_bytes());
1371 value.extend_from_slice(&c.local_data2.to_be_bytes());
1372 }
1373 }
1374 PathAttribute::OriginatorId(addr) => {
1375 flags = attr_flags::OPTIONAL;
1376 type_code = attr_type::ORIGINATOR_ID;
1377 value.extend_from_slice(&addr.octets());
1378 }
1379 PathAttribute::ClusterList(ids) => {
1380 flags = attr_flags::OPTIONAL;
1381 type_code = attr_type::CLUSTER_LIST;
1382 for id in ids {
1383 value.extend_from_slice(&id.octets());
1384 }
1385 }
1386 PathAttribute::MpReachNlri(mp) => {
1387 flags = attr_flags::OPTIONAL;
1388 type_code = attr_type::MP_REACH_NLRI;
1389 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1390 }
1391 PathAttribute::MpUnreachNlri(mp) => {
1392 flags = attr_flags::OPTIONAL;
1393 type_code = attr_type::MP_UNREACH_NLRI;
1394 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1395 }
1396 PathAttribute::Unknown(raw) => {
1397 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1401 flags = if (raw.flags & optional_transitive) == optional_transitive {
1402 raw.flags | attr_flags::PARTIAL
1403 } else {
1404 raw.flags
1405 };
1406 type_code = raw.type_code;
1407 value.extend_from_slice(&raw.data);
1408 }
1409 }
1410
1411 if value.len() > 255 {
1413 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1414 buf.push(type_code);
1415 #[expect(clippy::cast_possible_truncation)]
1416 let len = value.len() as u16;
1417 buf.extend_from_slice(&len.to_be_bytes());
1418 } else {
1419 buf.push(flags);
1420 buf.push(type_code);
1421 #[expect(clippy::cast_possible_truncation)]
1422 buf.push(value.len() as u8);
1423 }
1424 buf.extend_from_slice(&value);
1425 }
1426}
1427
1428fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1433 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1434 buf.push(mp.safi as u8);
1435
1436 if mp.safi == Safi::FlowSpec {
1438 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1441 return;
1442 }
1443
1444 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1446 match mp.next_hop {
1447 IpAddr::V4(addr) => {
1448 buf.push(4);
1449 buf.extend_from_slice(&addr.octets());
1450 }
1451 IpAddr::V6(addr) => {
1452 buf.push(16);
1453 buf.extend_from_slice(&addr.octets());
1454 }
1455 }
1456 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1458 return;
1459 }
1460
1461 match (mp.next_hop, mp.link_local_next_hop) {
1462 (IpAddr::V4(addr), _) => {
1463 buf.push(4); buf.extend_from_slice(&addr.octets());
1465 }
1466 (IpAddr::V6(addr), Some(ll)) => {
1467 debug_assert!(
1479 (ll.segments()[0] & 0xffc0) == 0xfe80,
1480 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1481 );
1482 buf.push(32); buf.extend_from_slice(&addr.octets());
1484 buf.extend_from_slice(&ll.octets());
1485 }
1486 (IpAddr::V6(addr), None) => {
1487 buf.push(16); buf.extend_from_slice(&addr.octets());
1489 }
1490 }
1491
1492 buf.push(0); if add_path {
1495 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1496 } else {
1497 for entry in &mp.announced {
1498 match entry.prefix {
1499 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1500 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1501 }
1502 }
1503 }
1504}
1505
1506fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1510 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1511 buf.push(mp.safi as u8);
1512
1513 if mp.safi == Safi::FlowSpec {
1515 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1516 return;
1517 }
1518
1519 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1521 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1522 return;
1523 }
1524
1525 if add_path {
1526 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1527 } else {
1528 for entry in &mp.withdrawn {
1529 match entry.prefix {
1530 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1531 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1532 }
1533 }
1534 }
1535}
1536
1537fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1539 for segment in &as_path.segments {
1540 let (seg_type, asns) = match segment {
1541 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1542 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1543 };
1544 for chunk in asns.chunks(u8::MAX as usize) {
1545 buf.push(seg_type);
1546 #[expect(clippy::cast_possible_truncation)]
1547 buf.push(chunk.len() as u8);
1548 for &asn in chunk {
1549 if four_octet_as {
1550 buf.extend_from_slice(&asn.to_be_bytes());
1551 } else {
1552 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1555 buf.extend_from_slice(&as2.to_be_bytes());
1556 }
1557 }
1558 }
1559 }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564 use super::*;
1565
1566 #[test]
1567 fn mp_reach_evpn_attribute_roundtrip() {
1568 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1569
1570 let mp = MpReachNlri {
1571 afi: Afi::L2Vpn,
1572 safi: Safi::Evpn,
1573 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1574 link_local_next_hop: None,
1575 announced: vec![],
1576 flowspec_announced: vec![],
1577 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1578 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1579 ethernet_tag: EthernetTagId(100),
1580 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1581 })],
1582 };
1583 let attr = PathAttribute::MpReachNlri(mp);
1584
1585 let mut buf = Vec::new();
1586 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1587 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1588 assert_eq!(decoded.len(), 1);
1589 assert_eq!(attr, decoded[0]);
1590
1591 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1592 panic!("not MP_REACH after decode");
1593 };
1594 assert_eq!(dec.afi, Afi::L2Vpn);
1595 assert_eq!(dec.safi, Safi::Evpn);
1596 assert_eq!(dec.evpn_announced.len(), 1);
1597 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1598 }
1599
1600 #[test]
1601 fn mp_unreach_evpn_attribute_roundtrip() {
1602 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1603
1604 let mp = MpUnreachNlri {
1605 afi: Afi::L2Vpn,
1606 safi: Safi::Evpn,
1607 withdrawn: vec![],
1608 flowspec_withdrawn: vec![],
1609 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1610 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1611 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1612 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1613 })],
1614 };
1615 let attr = PathAttribute::MpUnreachNlri(mp);
1616 let mut buf = Vec::new();
1617 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1618 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1619 assert_eq!(decoded.len(), 1);
1620 assert_eq!(attr, decoded[0]);
1621 }
1622
1623 #[test]
1626 fn ext_comm_bgp_encapsulation_vxlan() {
1627 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1629 assert_eq!(c.subtype(), 0x0C);
1630 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1631 let b = c.as_u64().to_be_bytes();
1633 assert_eq!(b[2..6], [0, 0, 0, 0]);
1634 assert_eq!(&b[6..8], &[0, 8]);
1635 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1637 }
1638
1639 #[test]
1640 fn ext_comm_mac_mobility_sticky_and_sequence() {
1641 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1642 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1643 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1644 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1645 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1647 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1648 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1649 }
1650
1651 #[test]
1652 fn ext_comm_esi_label_flags_and_label() {
1653 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1654 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1655 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1656 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1657 }
1658
1659 #[test]
1660 fn ext_comm_es_import_rt_mac() {
1661 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1662 let e = ExtendedCommunity::es_import_rt(mac);
1663 assert_eq!(e.as_es_import_rt(), Some(mac));
1664 assert_eq!(e.type_byte(), 0x06);
1665 assert_eq!(e.subtype(), 0x02);
1666 }
1667
1668 #[test]
1669 fn ext_comm_router_mac() {
1670 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1671 let e = ExtendedCommunity::router_mac(mac);
1672 assert_eq!(e.as_router_mac(), Some(mac));
1673 }
1674
1675 #[test]
1676 fn ext_comm_default_gateway_flag_only() {
1677 let d = ExtendedCommunity::default_gateway();
1678 assert!(d.as_default_gateway());
1679 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1681 }
1682
1683 #[test]
1687 fn ext_comm_default_gateway_rejects_nonzero_value() {
1688 let malformed =
1690 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1691 assert!(
1692 !malformed.as_default_gateway(),
1693 "default-gateway accessor must require all-zero value bytes"
1694 );
1695 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1697 }
1698
1699 #[test]
1700 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1701 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1703 assert_eq!(rt.as_mac_mobility(), None);
1704 assert_eq!(rt.as_esi_label(), None);
1705 assert_eq!(rt.as_es_import_rt(), None);
1706 assert_eq!(rt.as_router_mac(), None);
1707 assert!(!rt.as_default_gateway());
1708 }
1709
1710 #[test]
1711 fn origin_from_u8_roundtrip() {
1712 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1713 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1714 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1715 assert_eq!(Origin::from_u8(3), None);
1716 }
1717
1718 #[test]
1719 fn origin_ordering() {
1720 assert!(Origin::Igp < Origin::Egp);
1721 assert!(Origin::Egp < Origin::Incomplete);
1722 }
1723
1724 #[test]
1725 fn as_path_length_calculation() {
1726 let path = AsPath {
1727 segments: vec![
1728 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1729 AsPathSegment::AsSet(vec![65004, 65005]),
1730 ],
1731 };
1732 assert_eq!(path.len(), 4);
1734 }
1735
1736 #[test]
1737 fn as_path_empty() {
1738 let path = AsPath { segments: vec![] };
1739 assert!(path.is_empty());
1740 assert_eq!(path.len(), 0);
1741 }
1742
1743 #[test]
1744 fn contains_asn_in_sequence() {
1745 let path = AsPath {
1746 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1747 };
1748 assert!(path.contains_asn(65002));
1749 assert!(!path.contains_asn(65004));
1750 }
1751
1752 #[test]
1753 fn contains_asn_in_set() {
1754 let path = AsPath {
1755 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1756 };
1757 assert!(path.contains_asn(65005));
1758 assert!(!path.contains_asn(65001));
1759 }
1760
1761 #[test]
1762 fn contains_asn_multiple_segments() {
1763 let path = AsPath {
1764 segments: vec![
1765 AsPathSegment::AsSequence(vec![65001, 65002]),
1766 AsPathSegment::AsSet(vec![65003]),
1767 ],
1768 };
1769 assert!(path.contains_asn(65001));
1770 assert!(path.contains_asn(65003));
1771 assert!(!path.contains_asn(65004));
1772 }
1773
1774 #[test]
1775 fn contains_asn_empty_path() {
1776 let path = AsPath { segments: vec![] };
1777 assert!(!path.contains_asn(65001));
1778 }
1779
1780 #[test]
1781 fn is_private_asn_boundaries() {
1782 assert!(!is_private_asn(64_511));
1784 assert!(is_private_asn(64_512));
1785 assert!(is_private_asn(65_534));
1786 assert!(!is_private_asn(65_535));
1787
1788 assert!(!is_private_asn(4_199_999_999));
1790 assert!(is_private_asn(4_200_000_000));
1791 assert!(is_private_asn(4_294_967_294));
1792 assert!(!is_private_asn(4_294_967_295));
1793 }
1794
1795 #[test]
1796 fn all_private_empty_path_is_false() {
1797 let path = AsPath { segments: vec![] };
1798 assert!(!path.all_private());
1799 }
1800
1801 #[test]
1802 fn all_private_mixed_segments() {
1803 let path = AsPath {
1804 segments: vec![
1805 AsPathSegment::AsSet(vec![64_512, 65_000]),
1806 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1807 ],
1808 };
1809 assert!(path.all_private());
1810
1811 let non_private = AsPath {
1812 segments: vec![
1813 AsPathSegment::AsSet(vec![64_512, 65_000]),
1814 AsPathSegment::AsSequence(vec![65_535]),
1815 ],
1816 };
1817 assert!(!non_private.all_private());
1818 }
1819
1820 #[test]
1821 fn decode_origin_igp() {
1822 let buf = [0x40, 0x01, 0x01, 0x00];
1824 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1825 assert_eq!(attrs.len(), 1);
1826 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1827 }
1828
1829 #[test]
1830 fn decode_origin_egp() {
1831 let buf = [0x40, 0x01, 0x01, 0x01];
1832 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1833 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1834 }
1835
1836 #[test]
1837 fn decode_origin_invalid_value() {
1838 let buf = [0x40, 0x01, 0x01, 0x05];
1840 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1841 match &err {
1842 DecodeError::UpdateAttributeError { subcode, .. } => {
1843 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1844 }
1845 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1846 }
1847 }
1848
1849 #[test]
1850 fn decode_next_hop() {
1851 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1853 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1854 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1855 }
1856
1857 #[test]
1858 fn decode_med() {
1859 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1861 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1862 assert_eq!(attrs[0], PathAttribute::Med(100));
1863 }
1864
1865 #[test]
1866 fn decode_local_pref() {
1867 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
1869 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1870 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
1871 }
1872
1873 #[test]
1874 fn decode_as_path_4byte() {
1875 let buf = [
1878 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1883 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1884 assert_eq!(
1885 attrs[0],
1886 PathAttribute::AsPath(AsPath {
1887 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1888 })
1889 );
1890 }
1891
1892 #[test]
1893 fn decode_as_path_2byte() {
1894 let buf = [
1897 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
1902 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
1903 assert_eq!(
1904 attrs[0],
1905 PathAttribute::AsPath(AsPath {
1906 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
1907 })
1908 );
1909 }
1910
1911 #[test]
1912 fn decode_unknown_attribute_preserved() {
1913 let buf = [0xC0, 99, 0x03, 1, 2, 3];
1915 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1916 assert_eq!(
1917 attrs[0],
1918 PathAttribute::Unknown(RawAttribute {
1919 flags: 0xC0,
1920 type_code: 99,
1921 data: Bytes::from_static(&[1, 2, 3]),
1922 })
1923 );
1924 }
1925
1926 #[test]
1927 fn decode_atomic_aggregate_as_unknown() {
1928 let buf = [0x40, 0x06, 0x00];
1930 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1931 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
1932 }
1933
1934 #[test]
1935 fn decode_extended_length() {
1936 let buf = [
1939 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1944 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1945 assert_eq!(
1946 attrs[0],
1947 PathAttribute::AsPath(AsPath {
1948 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1949 })
1950 );
1951 }
1952
1953 #[test]
1954 fn decode_multiple_attributes() {
1955 let mut buf = Vec::new();
1956 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
1958 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
1960 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
1962
1963 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1964 assert_eq!(attrs.len(), 3);
1965 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1966 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1967 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
1968 }
1969
1970 #[test]
1971 fn roundtrip_attributes_4byte() {
1972 let attrs = vec![
1973 PathAttribute::Origin(Origin::Igp),
1974 PathAttribute::AsPath(AsPath {
1975 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
1976 }),
1977 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
1978 PathAttribute::Med(100),
1979 PathAttribute::LocalPref(200),
1980 ];
1981
1982 let mut buf = Vec::new();
1983 encode_path_attributes(&attrs, &mut buf, true, false);
1984 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1985 assert_eq!(decoded, attrs);
1986 }
1987
1988 #[test]
1989 fn roundtrip_attributes_2byte() {
1990 let attrs = vec![
1991 PathAttribute::Origin(Origin::Egp),
1992 PathAttribute::AsPath(AsPath {
1993 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
1994 }),
1995 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
1996 ];
1997
1998 let mut buf = Vec::new();
1999 encode_path_attributes(&attrs, &mut buf, false, false);
2000 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2001 assert_eq!(decoded, attrs);
2002 }
2003
2004 #[test]
2005 fn reject_truncated_attribute_header() {
2006 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2008 }
2009
2010 #[test]
2011 fn reject_truncated_attribute_value() {
2012 let buf = [0x40, 0x01, 0x01];
2014 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2015 }
2016
2017 #[test]
2018 fn reject_bad_origin_length() {
2019 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2021 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2022 }
2023
2024 #[test]
2025 fn as_path_with_set_and_sequence() {
2026 let attrs = vec![PathAttribute::AsPath(AsPath {
2028 segments: vec![
2029 AsPathSegment::AsSequence(vec![65001]),
2030 AsPathSegment::AsSet(vec![65002, 65003]),
2031 ],
2032 })];
2033
2034 let mut buf = Vec::new();
2035 encode_path_attributes(&attrs, &mut buf, true, false);
2036 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2037 assert_eq!(decoded, attrs);
2038 }
2039
2040 #[test]
2041 fn decode_communities_single() {
2042 let community: u32 = (65001 << 16) | 0x0064;
2045 let bytes = community.to_be_bytes();
2046 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2047 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2048 assert_eq!(attrs.len(), 1);
2049 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2050 }
2051
2052 #[test]
2053 fn decode_communities_multiple() {
2054 let c1: u32 = (65001 << 16) | 0x0064;
2055 let c2: u32 = (65002 << 16) | 0x00C8;
2056 let b1 = c1.to_be_bytes();
2057 let b2 = c2.to_be_bytes();
2058 let buf = [
2059 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2060 ];
2061 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2062 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2063 }
2064
2065 #[test]
2066 fn decode_communities_empty() {
2067 let buf = [0xC0, 0x08, 0x00];
2069 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2070 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2071 }
2072
2073 #[test]
2074 fn decode_communities_odd_length_rejected() {
2075 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2077 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2078 }
2079
2080 #[test]
2081 fn communities_roundtrip() {
2082 let c1: u32 = (65001 << 16) | 0x0064;
2083 let c2: u32 = (65002 << 16) | 0x00C8;
2084 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2085
2086 let mut buf = Vec::new();
2087 encode_path_attributes(&attrs, &mut buf, true, false);
2088 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2089 assert_eq!(decoded, attrs);
2090 }
2091
2092 #[test]
2093 fn communities_type_code_and_flags() {
2094 let attr = PathAttribute::Communities(vec![]);
2095 assert_eq!(attr.type_code(), 8);
2096 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2097 }
2098
2099 #[test]
2102 fn decode_extended_communities_single() {
2103 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2105 let bytes = ec.as_u64().to_be_bytes();
2106 let buf = [
2107 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2108 bytes[7],
2109 ];
2110 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2111 assert_eq!(attrs.len(), 1);
2112 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2113 }
2114
2115 #[test]
2116 fn decode_extended_communities_multiple() {
2117 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2118 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2119 let b1 = ec1.as_u64().to_be_bytes();
2120 let b2 = ec2.as_u64().to_be_bytes();
2121 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2123 buf.extend_from_slice(&b2);
2124 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2125 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2126 }
2127
2128 #[test]
2129 fn decode_extended_communities_empty() {
2130 let buf = [0xC0, 0x10, 0x00];
2131 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2132 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2133 }
2134
2135 #[test]
2136 fn decode_extended_communities_bad_length() {
2137 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2139 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2140 }
2141
2142 #[test]
2143 fn extended_communities_roundtrip() {
2144 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2145 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2146 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2147
2148 let mut buf = Vec::new();
2149 encode_path_attributes(&attrs, &mut buf, true, false);
2150 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2151 assert_eq!(decoded, attrs);
2152 }
2153
2154 #[test]
2155 fn extended_communities_type_code_and_flags() {
2156 let attr = PathAttribute::ExtendedCommunities(vec![]);
2157 assert_eq!(attr.type_code(), 16);
2158 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2159 }
2160
2161 #[test]
2162 fn extended_community_type_subtype() {
2163 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2165 assert_eq!(ec.type_byte(), 0x00);
2166 assert_eq!(ec.subtype(), 0x02);
2167 assert!(ec.is_transitive());
2168 }
2169
2170 #[test]
2171 fn extended_community_route_target() {
2172 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2174 assert_eq!(ec.route_target(), Some((65001, 100)));
2175 assert_eq!(ec.route_origin(), None);
2176
2177 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2179 assert_eq!(ec4.route_target(), Some((65537, 200)));
2180
2181 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2184 let (g, l) = ec_ipv4.route_target().unwrap();
2185 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2187 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2189 }
2190
2191 #[test]
2192 fn extended_community_is_transitive() {
2193 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2195 assert!(t.is_transitive());
2196
2197 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2199 assert!(!nt.is_transitive());
2200 }
2201
2202 #[test]
2203 fn extended_community_display() {
2204 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2205 assert_eq!(rt.to_string(), "RT:65001:100");
2206
2207 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2208 assert_eq!(ro.to_string(), "RO:65001:100");
2209
2210 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2212 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2213
2214 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2216 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2217
2218 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2220 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2221
2222 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2224 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2225 }
2226
2227 #[test]
2228 fn unknown_attribute_roundtrip() {
2229 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2232 flags: 0xC0,
2233 type_code: 99,
2234 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2235 })];
2236
2237 let mut buf = Vec::new();
2238 encode_path_attributes(&attrs, &mut buf, true, false);
2239 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2240 assert_eq!(
2241 decoded,
2242 vec![PathAttribute::Unknown(RawAttribute {
2243 flags: 0xE0, type_code: 99,
2245 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2246 })]
2247 );
2248 }
2249
2250 #[test]
2251 fn origin_with_optional_flag_rejected() {
2252 let buf = [0xC0, 0x01, 0x01, 0x00];
2254 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2255 match &err {
2256 DecodeError::UpdateAttributeError { subcode, .. } => {
2257 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2258 }
2259 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2260 }
2261 }
2262
2263 #[test]
2264 fn med_with_transitive_flag_rejected() {
2265 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2267 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2268 match &err {
2269 DecodeError::UpdateAttributeError { subcode, .. } => {
2270 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2271 }
2272 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2273 }
2274 }
2275
2276 #[test]
2277 fn communities_without_optional_rejected() {
2278 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2280 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2281 match &err {
2282 DecodeError::UpdateAttributeError { subcode, .. } => {
2283 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2284 }
2285 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2286 }
2287 }
2288
2289 #[test]
2290 fn next_hop_length_error_subcode() {
2291 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2293 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2294 match &err {
2295 DecodeError::UpdateAttributeError { subcode, .. } => {
2296 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2297 }
2298 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2299 }
2300 }
2301
2302 #[test]
2303 fn invalid_origin_value_subcode() {
2304 let buf = [0x40, 0x01, 0x01, 0x05];
2306 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2307 match &err {
2308 DecodeError::UpdateAttributeError { subcode, .. } => {
2309 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2310 }
2311 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2312 }
2313 }
2314
2315 #[test]
2316 fn as_path_bad_segment_subcode() {
2317 let buf = [
2319 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2323 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2324 match &err {
2325 DecodeError::UpdateAttributeError { subcode, .. } => {
2326 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2327 }
2328 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2329 }
2330 }
2331
2332 #[test]
2333 fn encode_unknown_transitive_sets_partial() {
2334 let attr = PathAttribute::Unknown(RawAttribute {
2335 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2337 data: Bytes::from_static(&[1, 2]),
2338 });
2339 let mut buf = Vec::new();
2340 encode_path_attributes(&[attr], &mut buf, true, false);
2341 assert_eq!(
2343 buf[0],
2344 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2345 );
2346 }
2347
2348 #[test]
2349 fn encode_unknown_wellknown_transitive_no_partial() {
2350 let attr = PathAttribute::Unknown(RawAttribute {
2352 flags: attr_flags::TRANSITIVE, type_code: 99,
2354 data: Bytes::from_static(&[1, 2]),
2355 });
2356 let mut buf = Vec::new();
2357 encode_path_attributes(&[attr], &mut buf, true, false);
2358 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2359 }
2360
2361 #[test]
2362 fn encode_unknown_nontransitive_no_partial() {
2363 let attr = PathAttribute::Unknown(RawAttribute {
2364 flags: attr_flags::OPTIONAL, type_code: 99,
2366 data: Bytes::from_static(&[1, 2]),
2367 });
2368 let mut buf = Vec::new();
2369 encode_path_attributes(&[attr], &mut buf, true, false);
2370 assert_eq!(buf[0], attr_flags::OPTIONAL);
2372 }
2373
2374 fn nlri(prefix: Prefix) -> NlriEntry {
2378 NlriEntry { path_id: 0, prefix }
2379 }
2380
2381 #[test]
2382 fn mp_reach_nlri_ipv6_roundtrip() {
2383 use crate::capability::{Afi, Safi};
2384 use crate::nlri::{Ipv6Prefix, Prefix};
2385
2386 let mp = MpReachNlri {
2387 afi: Afi::Ipv6,
2388 safi: Safi::Unicast,
2389 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2390 link_local_next_hop: None,
2391 announced: vec![
2392 nlri(Prefix::V6(Ipv6Prefix::new(
2393 "2001:db8:1::".parse().unwrap(),
2394 48,
2395 ))),
2396 nlri(Prefix::V6(Ipv6Prefix::new(
2397 "2001:db8:2::".parse().unwrap(),
2398 48,
2399 ))),
2400 ],
2401 flowspec_announced: vec![],
2402 evpn_announced: vec![],
2403 };
2404 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2405
2406 let mut buf = Vec::new();
2407 encode_path_attributes(&attrs, &mut buf, true, false);
2408 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2409 assert_eq!(decoded.len(), 1);
2410 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2411 }
2412
2413 #[test]
2414 fn mp_unreach_nlri_ipv6_roundtrip() {
2415 use crate::capability::{Afi, Safi};
2416 use crate::nlri::{Ipv6Prefix, Prefix};
2417
2418 let mp = MpUnreachNlri {
2419 afi: Afi::Ipv6,
2420 safi: Safi::Unicast,
2421 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2422 "2001:db8:1::".parse().unwrap(),
2423 48,
2424 )))],
2425 flowspec_withdrawn: vec![],
2426 evpn_withdrawn: vec![],
2427 };
2428 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2429
2430 let mut buf = Vec::new();
2431 encode_path_attributes(&attrs, &mut buf, true, false);
2432 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2433 assert_eq!(decoded.len(), 1);
2434 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2435 }
2436
2437 #[test]
2438 fn mp_reach_nlri_ipv4_roundtrip() {
2439 use crate::capability::{Afi, Safi};
2440 use crate::nlri::Prefix;
2441
2442 let mp = MpReachNlri {
2443 afi: Afi::Ipv4,
2444 safi: Safi::Unicast,
2445 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2446 link_local_next_hop: None,
2447 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2448 Ipv4Addr::new(10, 1, 0, 0),
2449 16,
2450 )))],
2451 flowspec_announced: vec![],
2452 evpn_announced: vec![],
2453 };
2454 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2455
2456 let mut buf = Vec::new();
2457 encode_path_attributes(&attrs, &mut buf, true, false);
2458 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2459 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2460 }
2461
2462 #[test]
2463 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2464 use crate::capability::{Afi, Safi};
2465 use crate::nlri::Prefix;
2466
2467 let mp = MpReachNlri {
2468 afi: Afi::Ipv4,
2469 safi: Safi::Unicast,
2470 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2471 link_local_next_hop: None,
2472 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2473 Ipv4Addr::new(10, 1, 0, 0),
2474 16,
2475 )))],
2476 flowspec_announced: vec![],
2477 evpn_announced: vec![],
2478 };
2479 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2480
2481 let mut buf = Vec::new();
2482 encode_path_attributes(&attrs, &mut buf, true, false);
2483 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2484 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2485 }
2486
2487 #[test]
2488 fn mp_reach_nlri_type_code_and_flags() {
2489 use crate::capability::{Afi, Safi};
2490
2491 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2492 afi: Afi::Ipv6,
2493 safi: Safi::Unicast,
2494 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2495 link_local_next_hop: None,
2496 announced: vec![],
2497 flowspec_announced: vec![],
2498 evpn_announced: vec![],
2499 });
2500 assert_eq!(attr.type_code(), 14);
2501 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2503 }
2504
2505 #[test]
2506 fn mp_unreach_nlri_type_code_and_flags() {
2507 use crate::capability::{Afi, Safi};
2508
2509 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2510 afi: Afi::Ipv6,
2511 safi: Safi::Unicast,
2512 withdrawn: vec![],
2513 flowspec_withdrawn: vec![],
2514 evpn_withdrawn: vec![],
2515 });
2516 assert_eq!(attr.type_code(), 15);
2517 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2518 }
2519
2520 #[test]
2521 fn mp_reach_nlri_empty_nlri() {
2522 use crate::capability::{Afi, Safi};
2523
2524 let mp = MpReachNlri {
2525 afi: Afi::Ipv6,
2526 safi: Safi::Unicast,
2527 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2528 link_local_next_hop: None,
2529 announced: vec![],
2530 flowspec_announced: vec![],
2531 evpn_announced: vec![],
2532 };
2533 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2534
2535 let mut buf = Vec::new();
2536 encode_path_attributes(&attrs, &mut buf, true, false);
2537 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2538 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2539 }
2540
2541 #[test]
2542 fn mp_reach_nlri_bad_flags_rejected() {
2543 let mut value = Vec::new();
2547 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();
2554 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2557 buf.push(value.len() as u8);
2558 buf.extend_from_slice(&value);
2559
2560 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2561 assert!(matches!(
2562 err,
2563 DecodeError::UpdateAttributeError {
2564 subcode: 4, ..
2566 }
2567 ));
2568 }
2569
2570 #[test]
2573 #[expect(clippy::cast_possible_truncation)]
2574 fn mp_reach_nlri_ipv4_addpath_decode() {
2575 use crate::capability::{Afi, Safi};
2576 use crate::nlri::Prefix;
2577
2578 let mut value = Vec::new();
2581 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());
2588 value.push(16);
2589 value.extend_from_slice(&[10, 1]);
2590
2591 let mut buf = Vec::new();
2592 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2595 buf.extend_from_slice(&value);
2596
2597 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2599 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2600 panic!("expected MpReachNlri");
2601 };
2602 assert_eq!(mp.announced.len(), 1);
2603 assert_eq!(mp.announced[0].path_id, 42);
2604 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2605
2606 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2609 }
2610
2611 #[test]
2612 #[expect(clippy::cast_possible_truncation)]
2613 fn mp_reach_nlri_ipv6_addpath_decode() {
2614 use crate::capability::{Afi, Safi};
2615 use crate::nlri::{Ipv6Prefix, Prefix};
2616
2617 let mut value = Vec::new();
2619 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());
2623 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2626 value.push(48);
2627 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2628
2629 let mut buf = Vec::new();
2630 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2633 buf.extend_from_slice(&value);
2634
2635 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2636 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2637 panic!("expected MpReachNlri");
2638 };
2639 assert_eq!(mp.announced.len(), 1);
2640 assert_eq!(mp.announced[0].path_id, 99);
2641 assert_eq!(
2642 mp.announced[0].prefix,
2643 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2644 );
2645 }
2646
2647 #[test]
2648 #[expect(clippy::cast_possible_truncation)]
2649 fn mp_unreach_nlri_ipv6_addpath_decode() {
2650 use crate::capability::{Afi, Safi};
2651 use crate::nlri::{Ipv6Prefix, Prefix};
2652
2653 let mut value = Vec::new();
2655 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2659 value.push(48);
2660 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2661
2662 let mut buf = Vec::new();
2663 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2666 buf.extend_from_slice(&value);
2667
2668 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2669 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2670 panic!("expected MpUnreachNlri");
2671 };
2672 assert_eq!(mp.withdrawn.len(), 1);
2673 assert_eq!(mp.withdrawn[0].path_id, 7);
2674 assert_eq!(
2675 mp.withdrawn[0].prefix,
2676 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2677 );
2678 }
2679
2680 #[test]
2681 fn mp_reach_addpath_only_applies_to_matching_family() {
2682 use crate::capability::{Afi, Safi};
2683 use crate::nlri::{Ipv6Prefix, Prefix};
2684
2685 let mp = MpReachNlri {
2687 afi: Afi::Ipv6,
2688 safi: Safi::Unicast,
2689 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2690 link_local_next_hop: None,
2691 announced: vec![NlriEntry {
2692 path_id: 0,
2693 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2694 }],
2695 flowspec_announced: vec![],
2696 evpn_announced: vec![],
2697 };
2698 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2699
2700 let mut buf = Vec::new();
2701 encode_path_attributes(&attrs, &mut buf, true, false);
2702
2703 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2705 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2706 }
2707
2708 #[test]
2711 fn decode_originator_id() {
2712 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2714 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2715 assert_eq!(
2716 attrs[0],
2717 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2718 );
2719 }
2720
2721 #[test]
2726 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
2727 use crate::capability::{Afi, Safi};
2728 use crate::nlri::{Ipv6Prefix, Prefix};
2729 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
2730 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
2731 let mp = MpReachNlri {
2732 afi: Afi::Ipv6,
2733 safi: Safi::Unicast,
2734 next_hop: IpAddr::V6(global),
2735 link_local_next_hop: Some(link_local),
2736 announced: vec![NlriEntry {
2737 path_id: 0,
2738 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2739 }],
2740 flowspec_announced: vec![],
2741 evpn_announced: vec![],
2742 };
2743 let attr = PathAttribute::MpReachNlri(mp.clone());
2744 let mut buf = Vec::new();
2745 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2746
2747 let extended = (buf[0] & 0x10) != 0;
2751 let value_off = if extended { 4 } else { 3 };
2752 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
2754 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
2755 assert_eq!(
2756 &buf[value_off + 20..value_off + 36],
2757 &link_local.octets(),
2758 "encoded link-local bytes must match the input"
2759 );
2760
2761 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2762 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
2763 panic!("expected MpReachNlri");
2764 };
2765 assert_eq!(dec.next_hop, IpAddr::V6(global));
2766 assert_eq!(dec.link_local_next_hop, Some(link_local));
2767 }
2768
2769 #[test]
2777 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
2778 let value: &[u8] = &[
2781 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
2788 ];
2789 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
2792 attr.extend_from_slice(value);
2793 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
2794 match err {
2795 DecodeError::MalformedField { detail, .. } => {
2796 assert!(
2797 detail.contains("FlowSpec next-hop length"),
2798 "expected FlowSpec NH-Len rejection, got: {detail}"
2799 );
2800 }
2801 other => panic!("expected MalformedField, got {other:?}"),
2802 }
2803 }
2804
2805 #[test]
2806 fn originator_id_roundtrip() {
2807 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2808 let mut buf = Vec::new();
2809 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2810 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2811 assert_eq!(decoded, vec![attr]);
2812 }
2813
2814 #[test]
2815 fn originator_id_wrong_length() {
2816 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2818 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2819 assert!(matches!(
2820 err,
2821 DecodeError::UpdateAttributeError {
2822 subcode: 5, ..
2824 }
2825 ));
2826 }
2827
2828 #[test]
2829 fn originator_id_wrong_flags() {
2830 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2832 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2833 assert!(matches!(
2834 err,
2835 DecodeError::UpdateAttributeError {
2836 subcode: 4, ..
2838 }
2839 ));
2840 }
2841
2842 #[test]
2845 fn decode_cluster_list() {
2846 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2848 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2849 assert_eq!(
2850 attrs[0],
2851 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2852 );
2853 }
2854
2855 #[test]
2856 fn cluster_list_roundtrip() {
2857 let attr = PathAttribute::ClusterList(vec![
2858 Ipv4Addr::new(10, 0, 0, 1),
2859 Ipv4Addr::new(10, 0, 0, 2),
2860 ]);
2861 let mut buf = Vec::new();
2862 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2863 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2864 assert_eq!(decoded, vec![attr]);
2865 }
2866
2867 #[test]
2868 fn cluster_list_wrong_length() {
2869 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
2871 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2872 assert!(matches!(
2873 err,
2874 DecodeError::UpdateAttributeError {
2875 subcode: 5, ..
2877 }
2878 ));
2879 }
2880
2881 #[test]
2886 fn large_community_display() {
2887 let lc = LargeCommunity::new(65001, 100, 200);
2888 assert_eq!(lc.to_string(), "65001:100:200");
2889 }
2890
2891 #[test]
2892 fn large_community_type_code_and_flags() {
2893 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
2894 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
2895 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2896 }
2897
2898 #[test]
2899 fn decode_large_community_single() {
2900 let mut buf = vec![0xC0, 32, 12];
2902 buf.extend_from_slice(&65001u32.to_be_bytes());
2903 buf.extend_from_slice(&100u32.to_be_bytes());
2904 buf.extend_from_slice(&200u32.to_be_bytes());
2905 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2906 assert_eq!(attrs.len(), 1);
2907 assert_eq!(
2908 attrs[0],
2909 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
2910 );
2911 }
2912
2913 #[test]
2914 fn decode_large_community_multiple() {
2915 let mut buf = vec![0xC0, 32, 24];
2917 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
2918 buf.extend_from_slice(&g.to_be_bytes());
2919 buf.extend_from_slice(&l1.to_be_bytes());
2920 buf.extend_from_slice(&l2.to_be_bytes());
2921 }
2922 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2923 assert_eq!(
2924 attrs[0],
2925 PathAttribute::LargeCommunities(vec![
2926 LargeCommunity::new(65001, 100, 200),
2927 LargeCommunity::new(65002, 300, 400),
2928 ])
2929 );
2930 }
2931
2932 #[test]
2933 fn decode_large_community_bad_length() {
2934 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
2936 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2937 assert!(matches!(
2938 err,
2939 DecodeError::UpdateAttributeError {
2940 subcode: 5, ..
2942 }
2943 ));
2944 }
2945
2946 #[test]
2947 fn decode_large_community_empty_rejected() {
2948 let buf = [0xC0, 32, 0];
2950 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2951 assert!(matches!(
2952 err,
2953 DecodeError::UpdateAttributeError {
2954 subcode: 5, ..
2956 }
2957 ));
2958 }
2959
2960 #[test]
2961 fn large_community_roundtrip() {
2962 let lcs = vec![
2963 LargeCommunity::new(65001, 100, 200),
2964 LargeCommunity::new(0, u32::MAX, 42),
2965 ];
2966 let attr = PathAttribute::LargeCommunities(lcs.clone());
2967 let mut buf = Vec::new();
2968 encode_path_attributes(&[attr], &mut buf, true, false);
2969 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2970 assert_eq!(decoded.len(), 1);
2971 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
2972 }
2973
2974 #[test]
2975 fn large_community_expected_flags_validated() {
2976 let mut buf = vec![0x40, 32, 12];
2978 buf.extend_from_slice(&1u32.to_be_bytes());
2979 buf.extend_from_slice(&2u32.to_be_bytes());
2980 buf.extend_from_slice(&3u32.to_be_bytes());
2981 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2982 assert!(matches!(
2983 err,
2984 DecodeError::UpdateAttributeError {
2985 subcode: 4, ..
2987 }
2988 ));
2989 }
2990
2991 #[test]
2996 fn aspath_string_sequence() {
2997 let p = AsPath {
2998 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2999 };
3000 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3001 }
3002
3003 #[test]
3004 fn aspath_string_set() {
3005 let p = AsPath {
3006 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3007 };
3008 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3009 }
3010
3011 #[test]
3012 fn aspath_string_mixed() {
3013 let p = AsPath {
3014 segments: vec![
3015 AsPathSegment::AsSequence(vec![65001, 65002]),
3016 AsPathSegment::AsSet(vec![65003, 65004]),
3017 ],
3018 };
3019 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3020 }
3021
3022 #[test]
3023 fn aspath_string_empty() {
3024 let p = AsPath { segments: vec![] };
3025 assert_eq!(p.to_aspath_string(), "");
3026 }
3027
3028 #[test]
3033 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3034 let bytes = vec![
3037 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3043 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3044 match err {
3045 DecodeError::MalformedField { detail, .. } => {
3046 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3047 }
3048 other => panic!("expected MalformedField, got {other:?}"),
3049 }
3050 }
3051
3052 #[test]
3053 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3054 let bytes = vec![
3055 0x00, 0x02, 70, 3, 0, ];
3059 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3060 match err {
3061 DecodeError::MalformedField { detail, .. } => {
3062 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3063 }
3064 other => panic!("expected MalformedField, got {other:?}"),
3065 }
3066 }
3067}