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]
1611 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1612 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1613
1614 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1615 let mp = MpReachNlri {
1616 afi: Afi::L2Vpn,
1617 safi: Safi::Evpn,
1618 next_hop: IpAddr::V6(vtep_v6),
1619 link_local_next_hop: None,
1620 announced: vec![],
1621 flowspec_announced: vec![],
1622 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1623 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1624 ethernet_tag: EthernetTagId(100),
1625 originator_ip: IpAddr::V6(vtep_v6),
1626 })],
1627 };
1628 let attr = PathAttribute::MpReachNlri(mp.clone());
1629
1630 let mut buf = Vec::new();
1631 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1632
1633 let extended = (buf[0] & 0x10) != 0;
1644 let value_off = if extended { 4 } else { 3 };
1645 assert_eq!(
1646 buf[value_off + 3],
1647 16,
1648 "EVPN IPv6 NH-Len must be 16, not 32"
1649 );
1650 assert_eq!(
1651 &buf[value_off + 4..value_off + 20],
1652 &vtep_v6.octets(),
1653 "encoded VTEP next-hop bytes must match the input"
1654 );
1655
1656 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1657 assert_eq!(decoded.len(), 1);
1658 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1659
1660 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1661 panic!("not MP_REACH after decode");
1662 };
1663 assert_eq!(dec.afi, Afi::L2Vpn);
1664 assert_eq!(dec.safi, Safi::Evpn);
1665 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1666 assert!(
1667 dec.link_local_next_hop.is_none(),
1668 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1669 );
1670 assert_eq!(dec.evpn_announced.len(), 1);
1671 match &dec.evpn_announced[0] {
1672 EvpnRoute::Imet(imet) => {
1673 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1674 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1675 }
1676 other => panic!("expected IMET, got {other:?}"),
1677 }
1678 }
1679
1680 #[test]
1687 fn mp_reach_evpn_rejects_32byte_next_hop() {
1688 let mut attr = vec![0x80u8, 14, 37];
1694 attr.extend_from_slice(&[
1695 0x00, 0x19, 0x46, 0x20, ]);
1699 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1703 match err {
1704 DecodeError::MalformedField { detail, .. } => {
1705 assert!(
1706 detail.contains("L2VPN next-hop length 32"),
1707 "expected L2VPN NH-Len rejection, got: {detail}"
1708 );
1709 }
1710 other => panic!("expected MalformedField, got: {other:?}"),
1711 }
1712 }
1713
1714 #[test]
1715 fn mp_unreach_evpn_attribute_roundtrip() {
1716 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1717
1718 let mp = MpUnreachNlri {
1719 afi: Afi::L2Vpn,
1720 safi: Safi::Evpn,
1721 withdrawn: vec![],
1722 flowspec_withdrawn: vec![],
1723 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1724 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1725 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1726 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1727 })],
1728 };
1729 let attr = PathAttribute::MpUnreachNlri(mp);
1730 let mut buf = Vec::new();
1731 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1732 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1733 assert_eq!(decoded.len(), 1);
1734 assert_eq!(attr, decoded[0]);
1735 }
1736
1737 #[test]
1740 fn ext_comm_bgp_encapsulation_vxlan() {
1741 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1743 assert_eq!(c.subtype(), 0x0C);
1744 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1745 let b = c.as_u64().to_be_bytes();
1747 assert_eq!(b[2..6], [0, 0, 0, 0]);
1748 assert_eq!(&b[6..8], &[0, 8]);
1749 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1751 }
1752
1753 #[test]
1754 fn ext_comm_mac_mobility_sticky_and_sequence() {
1755 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1756 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1757 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1758 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1759 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1761 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1762 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1763 }
1764
1765 #[test]
1766 fn ext_comm_esi_label_flags_and_label() {
1767 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1768 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1769 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1770 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1771 }
1772
1773 #[test]
1774 fn ext_comm_es_import_rt_mac() {
1775 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1776 let e = ExtendedCommunity::es_import_rt(mac);
1777 assert_eq!(e.as_es_import_rt(), Some(mac));
1778 assert_eq!(e.type_byte(), 0x06);
1779 assert_eq!(e.subtype(), 0x02);
1780 }
1781
1782 #[test]
1783 fn ext_comm_router_mac() {
1784 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1785 let e = ExtendedCommunity::router_mac(mac);
1786 assert_eq!(e.as_router_mac(), Some(mac));
1787 }
1788
1789 #[test]
1790 fn ext_comm_default_gateway_flag_only() {
1791 let d = ExtendedCommunity::default_gateway();
1792 assert!(d.as_default_gateway());
1793 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1795 }
1796
1797 #[test]
1801 fn ext_comm_default_gateway_rejects_nonzero_value() {
1802 let malformed =
1804 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1805 assert!(
1806 !malformed.as_default_gateway(),
1807 "default-gateway accessor must require all-zero value bytes"
1808 );
1809 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1811 }
1812
1813 #[test]
1814 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1815 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1817 assert_eq!(rt.as_mac_mobility(), None);
1818 assert_eq!(rt.as_esi_label(), None);
1819 assert_eq!(rt.as_es_import_rt(), None);
1820 assert_eq!(rt.as_router_mac(), None);
1821 assert!(!rt.as_default_gateway());
1822 }
1823
1824 #[test]
1825 fn origin_from_u8_roundtrip() {
1826 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1827 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1828 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1829 assert_eq!(Origin::from_u8(3), None);
1830 }
1831
1832 #[test]
1833 fn origin_ordering() {
1834 assert!(Origin::Igp < Origin::Egp);
1835 assert!(Origin::Egp < Origin::Incomplete);
1836 }
1837
1838 #[test]
1839 fn as_path_length_calculation() {
1840 let path = AsPath {
1841 segments: vec![
1842 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1843 AsPathSegment::AsSet(vec![65004, 65005]),
1844 ],
1845 };
1846 assert_eq!(path.len(), 4);
1848 }
1849
1850 #[test]
1851 fn as_path_empty() {
1852 let path = AsPath { segments: vec![] };
1853 assert!(path.is_empty());
1854 assert_eq!(path.len(), 0);
1855 }
1856
1857 #[test]
1858 fn contains_asn_in_sequence() {
1859 let path = AsPath {
1860 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1861 };
1862 assert!(path.contains_asn(65002));
1863 assert!(!path.contains_asn(65004));
1864 }
1865
1866 #[test]
1867 fn contains_asn_in_set() {
1868 let path = AsPath {
1869 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1870 };
1871 assert!(path.contains_asn(65005));
1872 assert!(!path.contains_asn(65001));
1873 }
1874
1875 #[test]
1876 fn contains_asn_multiple_segments() {
1877 let path = AsPath {
1878 segments: vec![
1879 AsPathSegment::AsSequence(vec![65001, 65002]),
1880 AsPathSegment::AsSet(vec![65003]),
1881 ],
1882 };
1883 assert!(path.contains_asn(65001));
1884 assert!(path.contains_asn(65003));
1885 assert!(!path.contains_asn(65004));
1886 }
1887
1888 #[test]
1889 fn contains_asn_empty_path() {
1890 let path = AsPath { segments: vec![] };
1891 assert!(!path.contains_asn(65001));
1892 }
1893
1894 #[test]
1895 fn is_private_asn_boundaries() {
1896 assert!(!is_private_asn(64_511));
1898 assert!(is_private_asn(64_512));
1899 assert!(is_private_asn(65_534));
1900 assert!(!is_private_asn(65_535));
1901
1902 assert!(!is_private_asn(4_199_999_999));
1904 assert!(is_private_asn(4_200_000_000));
1905 assert!(is_private_asn(4_294_967_294));
1906 assert!(!is_private_asn(4_294_967_295));
1907 }
1908
1909 #[test]
1910 fn all_private_empty_path_is_false() {
1911 let path = AsPath { segments: vec![] };
1912 assert!(!path.all_private());
1913 }
1914
1915 #[test]
1916 fn all_private_mixed_segments() {
1917 let path = AsPath {
1918 segments: vec![
1919 AsPathSegment::AsSet(vec![64_512, 65_000]),
1920 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1921 ],
1922 };
1923 assert!(path.all_private());
1924
1925 let non_private = AsPath {
1926 segments: vec![
1927 AsPathSegment::AsSet(vec![64_512, 65_000]),
1928 AsPathSegment::AsSequence(vec![65_535]),
1929 ],
1930 };
1931 assert!(!non_private.all_private());
1932 }
1933
1934 #[test]
1935 fn decode_origin_igp() {
1936 let buf = [0x40, 0x01, 0x01, 0x00];
1938 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1939 assert_eq!(attrs.len(), 1);
1940 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1941 }
1942
1943 #[test]
1944 fn decode_origin_egp() {
1945 let buf = [0x40, 0x01, 0x01, 0x01];
1946 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1947 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1948 }
1949
1950 #[test]
1951 fn decode_origin_invalid_value() {
1952 let buf = [0x40, 0x01, 0x01, 0x05];
1954 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1955 match &err {
1956 DecodeError::UpdateAttributeError { subcode, .. } => {
1957 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1958 }
1959 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1960 }
1961 }
1962
1963 #[test]
1964 fn decode_next_hop() {
1965 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1967 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1968 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1969 }
1970
1971 #[test]
1972 fn decode_med() {
1973 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1975 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1976 assert_eq!(attrs[0], PathAttribute::Med(100));
1977 }
1978
1979 #[test]
1980 fn decode_local_pref() {
1981 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
1983 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1984 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
1985 }
1986
1987 #[test]
1988 fn decode_as_path_4byte() {
1989 let buf = [
1992 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1997 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1998 assert_eq!(
1999 attrs[0],
2000 PathAttribute::AsPath(AsPath {
2001 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2002 })
2003 );
2004 }
2005
2006 #[test]
2007 fn decode_as_path_2byte() {
2008 let buf = [
2011 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2016 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2017 assert_eq!(
2018 attrs[0],
2019 PathAttribute::AsPath(AsPath {
2020 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2021 })
2022 );
2023 }
2024
2025 #[test]
2026 fn decode_unknown_attribute_preserved() {
2027 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2029 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2030 assert_eq!(
2031 attrs[0],
2032 PathAttribute::Unknown(RawAttribute {
2033 flags: 0xC0,
2034 type_code: 99,
2035 data: Bytes::from_static(&[1, 2, 3]),
2036 })
2037 );
2038 }
2039
2040 #[test]
2041 fn decode_atomic_aggregate_as_unknown() {
2042 let buf = [0x40, 0x06, 0x00];
2044 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2045 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2046 }
2047
2048 #[test]
2049 fn decode_extended_length() {
2050 let buf = [
2053 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2058 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2059 assert_eq!(
2060 attrs[0],
2061 PathAttribute::AsPath(AsPath {
2062 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2063 })
2064 );
2065 }
2066
2067 #[test]
2068 fn decode_multiple_attributes() {
2069 let mut buf = Vec::new();
2070 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2072 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2074 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2076
2077 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2078 assert_eq!(attrs.len(), 3);
2079 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2080 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2081 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2082 }
2083
2084 #[test]
2085 fn roundtrip_attributes_4byte() {
2086 let attrs = vec![
2087 PathAttribute::Origin(Origin::Igp),
2088 PathAttribute::AsPath(AsPath {
2089 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2090 }),
2091 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2092 PathAttribute::Med(100),
2093 PathAttribute::LocalPref(200),
2094 ];
2095
2096 let mut buf = Vec::new();
2097 encode_path_attributes(&attrs, &mut buf, true, false);
2098 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2099 assert_eq!(decoded, attrs);
2100 }
2101
2102 #[test]
2103 fn roundtrip_attributes_2byte() {
2104 let attrs = vec![
2105 PathAttribute::Origin(Origin::Egp),
2106 PathAttribute::AsPath(AsPath {
2107 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2108 }),
2109 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2110 ];
2111
2112 let mut buf = Vec::new();
2113 encode_path_attributes(&attrs, &mut buf, false, false);
2114 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2115 assert_eq!(decoded, attrs);
2116 }
2117
2118 #[test]
2119 fn reject_truncated_attribute_header() {
2120 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2122 }
2123
2124 #[test]
2125 fn reject_truncated_attribute_value() {
2126 let buf = [0x40, 0x01, 0x01];
2128 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2129 }
2130
2131 #[test]
2132 fn reject_bad_origin_length() {
2133 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2135 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2136 }
2137
2138 #[test]
2139 fn as_path_with_set_and_sequence() {
2140 let attrs = vec![PathAttribute::AsPath(AsPath {
2142 segments: vec![
2143 AsPathSegment::AsSequence(vec![65001]),
2144 AsPathSegment::AsSet(vec![65002, 65003]),
2145 ],
2146 })];
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 decode_communities_single() {
2156 let community: u32 = (65001 << 16) | 0x0064;
2159 let bytes = community.to_be_bytes();
2160 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2161 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2162 assert_eq!(attrs.len(), 1);
2163 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2164 }
2165
2166 #[test]
2167 fn decode_communities_multiple() {
2168 let c1: u32 = (65001 << 16) | 0x0064;
2169 let c2: u32 = (65002 << 16) | 0x00C8;
2170 let b1 = c1.to_be_bytes();
2171 let b2 = c2.to_be_bytes();
2172 let buf = [
2173 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2174 ];
2175 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2176 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2177 }
2178
2179 #[test]
2180 fn decode_communities_empty() {
2181 let buf = [0xC0, 0x08, 0x00];
2183 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2184 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2185 }
2186
2187 #[test]
2188 fn decode_communities_odd_length_rejected() {
2189 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2191 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2192 }
2193
2194 #[test]
2195 fn communities_roundtrip() {
2196 let c1: u32 = (65001 << 16) | 0x0064;
2197 let c2: u32 = (65002 << 16) | 0x00C8;
2198 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2199
2200 let mut buf = Vec::new();
2201 encode_path_attributes(&attrs, &mut buf, true, false);
2202 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2203 assert_eq!(decoded, attrs);
2204 }
2205
2206 #[test]
2207 fn communities_type_code_and_flags() {
2208 let attr = PathAttribute::Communities(vec![]);
2209 assert_eq!(attr.type_code(), 8);
2210 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2211 }
2212
2213 #[test]
2216 fn decode_extended_communities_single() {
2217 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2219 let bytes = ec.as_u64().to_be_bytes();
2220 let buf = [
2221 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2222 bytes[7],
2223 ];
2224 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2225 assert_eq!(attrs.len(), 1);
2226 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2227 }
2228
2229 #[test]
2230 fn decode_extended_communities_multiple() {
2231 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2232 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2233 let b1 = ec1.as_u64().to_be_bytes();
2234 let b2 = ec2.as_u64().to_be_bytes();
2235 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2237 buf.extend_from_slice(&b2);
2238 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2239 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2240 }
2241
2242 #[test]
2243 fn decode_extended_communities_empty() {
2244 let buf = [0xC0, 0x10, 0x00];
2245 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2246 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2247 }
2248
2249 #[test]
2250 fn decode_extended_communities_bad_length() {
2251 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2253 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2254 }
2255
2256 #[test]
2257 fn extended_communities_roundtrip() {
2258 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2259 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2260 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2261
2262 let mut buf = Vec::new();
2263 encode_path_attributes(&attrs, &mut buf, true, false);
2264 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2265 assert_eq!(decoded, attrs);
2266 }
2267
2268 #[test]
2269 fn extended_communities_type_code_and_flags() {
2270 let attr = PathAttribute::ExtendedCommunities(vec![]);
2271 assert_eq!(attr.type_code(), 16);
2272 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2273 }
2274
2275 #[test]
2276 fn extended_community_type_subtype() {
2277 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2279 assert_eq!(ec.type_byte(), 0x00);
2280 assert_eq!(ec.subtype(), 0x02);
2281 assert!(ec.is_transitive());
2282 }
2283
2284 #[test]
2285 fn extended_community_route_target() {
2286 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2288 assert_eq!(ec.route_target(), Some((65001, 100)));
2289 assert_eq!(ec.route_origin(), None);
2290
2291 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2293 assert_eq!(ec4.route_target(), Some((65537, 200)));
2294
2295 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2298 let (g, l) = ec_ipv4.route_target().unwrap();
2299 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2301 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2303 }
2304
2305 #[test]
2306 fn extended_community_is_transitive() {
2307 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2309 assert!(t.is_transitive());
2310
2311 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2313 assert!(!nt.is_transitive());
2314 }
2315
2316 #[test]
2317 fn extended_community_display() {
2318 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2319 assert_eq!(rt.to_string(), "RT:65001:100");
2320
2321 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2322 assert_eq!(ro.to_string(), "RO:65001:100");
2323
2324 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2326 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2327
2328 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2330 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2331
2332 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2334 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2335
2336 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2338 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2339 }
2340
2341 #[test]
2342 fn unknown_attribute_roundtrip() {
2343 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2346 flags: 0xC0,
2347 type_code: 99,
2348 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2349 })];
2350
2351 let mut buf = Vec::new();
2352 encode_path_attributes(&attrs, &mut buf, true, false);
2353 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2354 assert_eq!(
2355 decoded,
2356 vec![PathAttribute::Unknown(RawAttribute {
2357 flags: 0xE0, type_code: 99,
2359 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2360 })]
2361 );
2362 }
2363
2364 #[test]
2365 fn origin_with_optional_flag_rejected() {
2366 let buf = [0xC0, 0x01, 0x01, 0x00];
2368 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2369 match &err {
2370 DecodeError::UpdateAttributeError { subcode, .. } => {
2371 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2372 }
2373 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2374 }
2375 }
2376
2377 #[test]
2378 fn med_with_transitive_flag_rejected() {
2379 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2381 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2382 match &err {
2383 DecodeError::UpdateAttributeError { subcode, .. } => {
2384 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2385 }
2386 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2387 }
2388 }
2389
2390 #[test]
2391 fn communities_without_optional_rejected() {
2392 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2394 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2395 match &err {
2396 DecodeError::UpdateAttributeError { subcode, .. } => {
2397 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2398 }
2399 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2400 }
2401 }
2402
2403 #[test]
2404 fn next_hop_length_error_subcode() {
2405 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2407 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2408 match &err {
2409 DecodeError::UpdateAttributeError { subcode, .. } => {
2410 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2411 }
2412 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2413 }
2414 }
2415
2416 #[test]
2417 fn invalid_origin_value_subcode() {
2418 let buf = [0x40, 0x01, 0x01, 0x05];
2420 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2421 match &err {
2422 DecodeError::UpdateAttributeError { subcode, .. } => {
2423 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2424 }
2425 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2426 }
2427 }
2428
2429 #[test]
2430 fn as_path_bad_segment_subcode() {
2431 let buf = [
2433 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2437 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2438 match &err {
2439 DecodeError::UpdateAttributeError { subcode, .. } => {
2440 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2441 }
2442 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2443 }
2444 }
2445
2446 #[test]
2447 fn encode_unknown_transitive_sets_partial() {
2448 let attr = PathAttribute::Unknown(RawAttribute {
2449 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2451 data: Bytes::from_static(&[1, 2]),
2452 });
2453 let mut buf = Vec::new();
2454 encode_path_attributes(&[attr], &mut buf, true, false);
2455 assert_eq!(
2457 buf[0],
2458 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2459 );
2460 }
2461
2462 #[test]
2463 fn encode_unknown_wellknown_transitive_no_partial() {
2464 let attr = PathAttribute::Unknown(RawAttribute {
2466 flags: attr_flags::TRANSITIVE, type_code: 99,
2468 data: Bytes::from_static(&[1, 2]),
2469 });
2470 let mut buf = Vec::new();
2471 encode_path_attributes(&[attr], &mut buf, true, false);
2472 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2473 }
2474
2475 #[test]
2476 fn encode_unknown_nontransitive_no_partial() {
2477 let attr = PathAttribute::Unknown(RawAttribute {
2478 flags: attr_flags::OPTIONAL, type_code: 99,
2480 data: Bytes::from_static(&[1, 2]),
2481 });
2482 let mut buf = Vec::new();
2483 encode_path_attributes(&[attr], &mut buf, true, false);
2484 assert_eq!(buf[0], attr_flags::OPTIONAL);
2486 }
2487
2488 fn nlri(prefix: Prefix) -> NlriEntry {
2492 NlriEntry { path_id: 0, prefix }
2493 }
2494
2495 #[test]
2496 fn mp_reach_nlri_ipv6_roundtrip() {
2497 use crate::capability::{Afi, Safi};
2498 use crate::nlri::{Ipv6Prefix, Prefix};
2499
2500 let mp = MpReachNlri {
2501 afi: Afi::Ipv6,
2502 safi: Safi::Unicast,
2503 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2504 link_local_next_hop: None,
2505 announced: vec![
2506 nlri(Prefix::V6(Ipv6Prefix::new(
2507 "2001:db8:1::".parse().unwrap(),
2508 48,
2509 ))),
2510 nlri(Prefix::V6(Ipv6Prefix::new(
2511 "2001:db8:2::".parse().unwrap(),
2512 48,
2513 ))),
2514 ],
2515 flowspec_announced: vec![],
2516 evpn_announced: vec![],
2517 };
2518 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2519
2520 let mut buf = Vec::new();
2521 encode_path_attributes(&attrs, &mut buf, true, false);
2522 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2523 assert_eq!(decoded.len(), 1);
2524 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2525 }
2526
2527 #[test]
2528 fn mp_unreach_nlri_ipv6_roundtrip() {
2529 use crate::capability::{Afi, Safi};
2530 use crate::nlri::{Ipv6Prefix, Prefix};
2531
2532 let mp = MpUnreachNlri {
2533 afi: Afi::Ipv6,
2534 safi: Safi::Unicast,
2535 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2536 "2001:db8:1::".parse().unwrap(),
2537 48,
2538 )))],
2539 flowspec_withdrawn: vec![],
2540 evpn_withdrawn: vec![],
2541 };
2542 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2543
2544 let mut buf = Vec::new();
2545 encode_path_attributes(&attrs, &mut buf, true, false);
2546 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2547 assert_eq!(decoded.len(), 1);
2548 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2549 }
2550
2551 #[test]
2552 fn mp_reach_nlri_ipv4_roundtrip() {
2553 use crate::capability::{Afi, Safi};
2554 use crate::nlri::Prefix;
2555
2556 let mp = MpReachNlri {
2557 afi: Afi::Ipv4,
2558 safi: Safi::Unicast,
2559 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2560 link_local_next_hop: None,
2561 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2562 Ipv4Addr::new(10, 1, 0, 0),
2563 16,
2564 )))],
2565 flowspec_announced: vec![],
2566 evpn_announced: vec![],
2567 };
2568 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2569
2570 let mut buf = Vec::new();
2571 encode_path_attributes(&attrs, &mut buf, true, false);
2572 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2573 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2574 }
2575
2576 #[test]
2577 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2578 use crate::capability::{Afi, Safi};
2579 use crate::nlri::Prefix;
2580
2581 let mp = MpReachNlri {
2582 afi: Afi::Ipv4,
2583 safi: Safi::Unicast,
2584 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2585 link_local_next_hop: None,
2586 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2587 Ipv4Addr::new(10, 1, 0, 0),
2588 16,
2589 )))],
2590 flowspec_announced: vec![],
2591 evpn_announced: vec![],
2592 };
2593 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2594
2595 let mut buf = Vec::new();
2596 encode_path_attributes(&attrs, &mut buf, true, false);
2597 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2598 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2599 }
2600
2601 #[test]
2602 fn mp_reach_nlri_type_code_and_flags() {
2603 use crate::capability::{Afi, Safi};
2604
2605 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2606 afi: Afi::Ipv6,
2607 safi: Safi::Unicast,
2608 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2609 link_local_next_hop: None,
2610 announced: vec![],
2611 flowspec_announced: vec![],
2612 evpn_announced: vec![],
2613 });
2614 assert_eq!(attr.type_code(), 14);
2615 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2617 }
2618
2619 #[test]
2620 fn mp_unreach_nlri_type_code_and_flags() {
2621 use crate::capability::{Afi, Safi};
2622
2623 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2624 afi: Afi::Ipv6,
2625 safi: Safi::Unicast,
2626 withdrawn: vec![],
2627 flowspec_withdrawn: vec![],
2628 evpn_withdrawn: vec![],
2629 });
2630 assert_eq!(attr.type_code(), 15);
2631 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2632 }
2633
2634 #[test]
2635 fn mp_reach_nlri_empty_nlri() {
2636 use crate::capability::{Afi, Safi};
2637
2638 let mp = MpReachNlri {
2639 afi: Afi::Ipv6,
2640 safi: Safi::Unicast,
2641 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2642 link_local_next_hop: None,
2643 announced: vec![],
2644 flowspec_announced: vec![],
2645 evpn_announced: vec![],
2646 };
2647 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2648
2649 let mut buf = Vec::new();
2650 encode_path_attributes(&attrs, &mut buf, true, false);
2651 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2652 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2653 }
2654
2655 #[test]
2656 fn mp_reach_nlri_bad_flags_rejected() {
2657 let mut value = Vec::new();
2661 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();
2668 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2671 buf.push(value.len() as u8);
2672 buf.extend_from_slice(&value);
2673
2674 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2675 assert!(matches!(
2676 err,
2677 DecodeError::UpdateAttributeError {
2678 subcode: 4, ..
2680 }
2681 ));
2682 }
2683
2684 #[test]
2687 #[expect(clippy::cast_possible_truncation)]
2688 fn mp_reach_nlri_ipv4_addpath_decode() {
2689 use crate::capability::{Afi, Safi};
2690 use crate::nlri::Prefix;
2691
2692 let mut value = Vec::new();
2695 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());
2702 value.push(16);
2703 value.extend_from_slice(&[10, 1]);
2704
2705 let mut buf = Vec::new();
2706 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2709 buf.extend_from_slice(&value);
2710
2711 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2713 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2714 panic!("expected MpReachNlri");
2715 };
2716 assert_eq!(mp.announced.len(), 1);
2717 assert_eq!(mp.announced[0].path_id, 42);
2718 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2719
2720 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2723 }
2724
2725 #[test]
2726 #[expect(clippy::cast_possible_truncation)]
2727 fn mp_reach_nlri_ipv6_addpath_decode() {
2728 use crate::capability::{Afi, Safi};
2729 use crate::nlri::{Ipv6Prefix, Prefix};
2730
2731 let mut value = Vec::new();
2733 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());
2737 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2740 value.push(48);
2741 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2742
2743 let mut buf = Vec::new();
2744 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2747 buf.extend_from_slice(&value);
2748
2749 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2750 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2751 panic!("expected MpReachNlri");
2752 };
2753 assert_eq!(mp.announced.len(), 1);
2754 assert_eq!(mp.announced[0].path_id, 99);
2755 assert_eq!(
2756 mp.announced[0].prefix,
2757 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2758 );
2759 }
2760
2761 #[test]
2762 #[expect(clippy::cast_possible_truncation)]
2763 fn mp_unreach_nlri_ipv6_addpath_decode() {
2764 use crate::capability::{Afi, Safi};
2765 use crate::nlri::{Ipv6Prefix, Prefix};
2766
2767 let mut value = Vec::new();
2769 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2773 value.push(48);
2774 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2775
2776 let mut buf = Vec::new();
2777 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2780 buf.extend_from_slice(&value);
2781
2782 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2783 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2784 panic!("expected MpUnreachNlri");
2785 };
2786 assert_eq!(mp.withdrawn.len(), 1);
2787 assert_eq!(mp.withdrawn[0].path_id, 7);
2788 assert_eq!(
2789 mp.withdrawn[0].prefix,
2790 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2791 );
2792 }
2793
2794 #[test]
2795 fn mp_reach_addpath_only_applies_to_matching_family() {
2796 use crate::capability::{Afi, Safi};
2797 use crate::nlri::{Ipv6Prefix, Prefix};
2798
2799 let mp = MpReachNlri {
2801 afi: Afi::Ipv6,
2802 safi: Safi::Unicast,
2803 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2804 link_local_next_hop: None,
2805 announced: vec![NlriEntry {
2806 path_id: 0,
2807 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2808 }],
2809 flowspec_announced: vec![],
2810 evpn_announced: vec![],
2811 };
2812 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2813
2814 let mut buf = Vec::new();
2815 encode_path_attributes(&attrs, &mut buf, true, false);
2816
2817 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2819 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2820 }
2821
2822 #[test]
2825 fn decode_originator_id() {
2826 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2828 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2829 assert_eq!(
2830 attrs[0],
2831 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2832 );
2833 }
2834
2835 #[test]
2840 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
2841 use crate::capability::{Afi, Safi};
2842 use crate::nlri::{Ipv6Prefix, Prefix};
2843 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
2844 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
2845 let mp = MpReachNlri {
2846 afi: Afi::Ipv6,
2847 safi: Safi::Unicast,
2848 next_hop: IpAddr::V6(global),
2849 link_local_next_hop: Some(link_local),
2850 announced: vec![NlriEntry {
2851 path_id: 0,
2852 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2853 }],
2854 flowspec_announced: vec![],
2855 evpn_announced: vec![],
2856 };
2857 let attr = PathAttribute::MpReachNlri(mp.clone());
2858 let mut buf = Vec::new();
2859 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2860
2861 let extended = (buf[0] & 0x10) != 0;
2865 let value_off = if extended { 4 } else { 3 };
2866 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
2868 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
2869 assert_eq!(
2870 &buf[value_off + 20..value_off + 36],
2871 &link_local.octets(),
2872 "encoded link-local bytes must match the input"
2873 );
2874
2875 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2876 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
2877 panic!("expected MpReachNlri");
2878 };
2879 assert_eq!(dec.next_hop, IpAddr::V6(global));
2880 assert_eq!(dec.link_local_next_hop, Some(link_local));
2881 }
2882
2883 #[test]
2891 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
2892 let value: &[u8] = &[
2895 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
2902 ];
2903 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
2906 attr.extend_from_slice(value);
2907 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
2908 match err {
2909 DecodeError::MalformedField { detail, .. } => {
2910 assert!(
2911 detail.contains("FlowSpec next-hop length"),
2912 "expected FlowSpec NH-Len rejection, got: {detail}"
2913 );
2914 }
2915 other => panic!("expected MalformedField, got {other:?}"),
2916 }
2917 }
2918
2919 #[test]
2920 fn originator_id_roundtrip() {
2921 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2922 let mut buf = Vec::new();
2923 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2924 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2925 assert_eq!(decoded, vec![attr]);
2926 }
2927
2928 #[test]
2929 fn originator_id_wrong_length() {
2930 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2932 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2933 assert!(matches!(
2934 err,
2935 DecodeError::UpdateAttributeError {
2936 subcode: 5, ..
2938 }
2939 ));
2940 }
2941
2942 #[test]
2943 fn originator_id_wrong_flags() {
2944 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2946 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2947 assert!(matches!(
2948 err,
2949 DecodeError::UpdateAttributeError {
2950 subcode: 4, ..
2952 }
2953 ));
2954 }
2955
2956 #[test]
2959 fn decode_cluster_list() {
2960 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2962 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2963 assert_eq!(
2964 attrs[0],
2965 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2966 );
2967 }
2968
2969 #[test]
2970 fn cluster_list_roundtrip() {
2971 let attr = PathAttribute::ClusterList(vec![
2972 Ipv4Addr::new(10, 0, 0, 1),
2973 Ipv4Addr::new(10, 0, 0, 2),
2974 ]);
2975 let mut buf = Vec::new();
2976 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2977 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2978 assert_eq!(decoded, vec![attr]);
2979 }
2980
2981 #[test]
2982 fn cluster_list_wrong_length() {
2983 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
2985 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2986 assert!(matches!(
2987 err,
2988 DecodeError::UpdateAttributeError {
2989 subcode: 5, ..
2991 }
2992 ));
2993 }
2994
2995 #[test]
3000 fn large_community_display() {
3001 let lc = LargeCommunity::new(65001, 100, 200);
3002 assert_eq!(lc.to_string(), "65001:100:200");
3003 }
3004
3005 #[test]
3006 fn large_community_type_code_and_flags() {
3007 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3008 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3009 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3010 }
3011
3012 #[test]
3013 fn decode_large_community_single() {
3014 let mut buf = vec![0xC0, 32, 12];
3016 buf.extend_from_slice(&65001u32.to_be_bytes());
3017 buf.extend_from_slice(&100u32.to_be_bytes());
3018 buf.extend_from_slice(&200u32.to_be_bytes());
3019 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3020 assert_eq!(attrs.len(), 1);
3021 assert_eq!(
3022 attrs[0],
3023 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3024 );
3025 }
3026
3027 #[test]
3028 fn decode_large_community_multiple() {
3029 let mut buf = vec![0xC0, 32, 24];
3031 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3032 buf.extend_from_slice(&g.to_be_bytes());
3033 buf.extend_from_slice(&l1.to_be_bytes());
3034 buf.extend_from_slice(&l2.to_be_bytes());
3035 }
3036 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3037 assert_eq!(
3038 attrs[0],
3039 PathAttribute::LargeCommunities(vec![
3040 LargeCommunity::new(65001, 100, 200),
3041 LargeCommunity::new(65002, 300, 400),
3042 ])
3043 );
3044 }
3045
3046 #[test]
3047 fn decode_large_community_bad_length() {
3048 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3050 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3051 assert!(matches!(
3052 err,
3053 DecodeError::UpdateAttributeError {
3054 subcode: 5, ..
3056 }
3057 ));
3058 }
3059
3060 #[test]
3061 fn decode_large_community_empty_rejected() {
3062 let buf = [0xC0, 32, 0];
3064 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3065 assert!(matches!(
3066 err,
3067 DecodeError::UpdateAttributeError {
3068 subcode: 5, ..
3070 }
3071 ));
3072 }
3073
3074 #[test]
3075 fn large_community_roundtrip() {
3076 let lcs = vec![
3077 LargeCommunity::new(65001, 100, 200),
3078 LargeCommunity::new(0, u32::MAX, 42),
3079 ];
3080 let attr = PathAttribute::LargeCommunities(lcs.clone());
3081 let mut buf = Vec::new();
3082 encode_path_attributes(&[attr], &mut buf, true, false);
3083 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3084 assert_eq!(decoded.len(), 1);
3085 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3086 }
3087
3088 #[test]
3089 fn large_community_expected_flags_validated() {
3090 let mut buf = vec![0x40, 32, 12];
3092 buf.extend_from_slice(&1u32.to_be_bytes());
3093 buf.extend_from_slice(&2u32.to_be_bytes());
3094 buf.extend_from_slice(&3u32.to_be_bytes());
3095 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3096 assert!(matches!(
3097 err,
3098 DecodeError::UpdateAttributeError {
3099 subcode: 4, ..
3101 }
3102 ));
3103 }
3104
3105 #[test]
3110 fn aspath_string_sequence() {
3111 let p = AsPath {
3112 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3113 };
3114 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3115 }
3116
3117 #[test]
3118 fn aspath_string_set() {
3119 let p = AsPath {
3120 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3121 };
3122 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3123 }
3124
3125 #[test]
3126 fn aspath_string_mixed() {
3127 let p = AsPath {
3128 segments: vec![
3129 AsPathSegment::AsSequence(vec![65001, 65002]),
3130 AsPathSegment::AsSet(vec![65003, 65004]),
3131 ],
3132 };
3133 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3134 }
3135
3136 #[test]
3137 fn aspath_string_empty() {
3138 let p = AsPath { segments: vec![] };
3139 assert_eq!(p.to_aspath_string(), "");
3140 }
3141
3142 #[test]
3147 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3148 let bytes = vec![
3151 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3157 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3158 match err {
3159 DecodeError::MalformedField { detail, .. } => {
3160 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3161 }
3162 other => panic!("expected MalformedField, got {other:?}"),
3163 }
3164 }
3165
3166 #[test]
3167 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3168 let bytes = vec![
3169 0x00, 0x02, 70, 3, 0, ];
3173 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3174 match err {
3175 DecodeError::MalformedField { detail, .. } => {
3176 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3177 }
3178 other => panic!("expected MalformedField, got {other:?}"),
3179 }
3180 }
3181}