1use std::fmt;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3
4use bytes::Bytes;
5
6use crate::capability::{Afi, Safi};
7use crate::constants::{as_path_segment, attr_flags, attr_type};
8use crate::error::DecodeError;
9use crate::nlri::{NlriEntry, Prefix};
10use crate::notification::update_subcode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u8)]
15pub enum Origin {
16 Igp = 0,
18 Egp = 1,
20 Incomplete = 2,
22}
23
24impl Origin {
25 #[must_use]
27 pub fn from_u8(value: u8) -> Option<Self> {
28 match value {
29 0 => Some(Self::Igp),
30 1 => Some(Self::Egp),
31 2 => Some(Self::Incomplete),
32 _ => None,
33 }
34 }
35}
36
37impl std::fmt::Display for Origin {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Igp => write!(f, "IGP"),
41 Self::Egp => write!(f, "EGP"),
42 Self::Incomplete => write!(f, "INCOMPLETE"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub enum AsPathSegment {
50 AsSet(Vec<u32>),
52 AsSequence(Vec<u32>),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct AsPath {
59 pub segments: Vec<AsPathSegment>,
61}
62
63impl AsPath {
64 #[must_use]
67 pub fn len(&self) -> usize {
68 self.segments
69 .iter()
70 .map(|seg| match seg {
71 AsPathSegment::AsSequence(asns) => asns.len(),
72 AsPathSegment::AsSet(_) => 1,
73 })
74 .sum()
75 }
76
77 #[must_use]
79 pub fn is_empty(&self) -> bool {
80 self.segments.is_empty()
81 }
82
83 #[must_use]
86 pub fn contains_asn(&self, asn: u32) -> bool {
87 self.segments.iter().any(|seg| match seg {
88 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => asns.contains(&asn),
89 })
90 }
91
92 #[must_use]
98 pub fn origin_asn(&self) -> Option<u32> {
99 self.segments.iter().rev().find_map(|seg| match seg {
100 AsPathSegment::AsSequence(asns) => asns.last().copied(),
101 AsPathSegment::AsSet(_) => None,
102 })
103 }
104
105 #[must_use]
109 pub fn all_private(&self) -> bool {
110 let mut count = 0;
111 for seg in &self.segments {
112 match seg {
113 AsPathSegment::AsSequence(asns) | AsPathSegment::AsSet(asns) => {
114 for asn in asns {
115 count += 1;
116 if !is_private_asn(*asn) {
117 return false;
118 }
119 }
120 }
121 }
122 }
123 count > 0
124 }
125
126 #[must_use]
134 pub fn to_aspath_string(&self) -> String {
135 let mut parts = Vec::new();
136 for seg in &self.segments {
137 match seg {
138 AsPathSegment::AsSequence(asns) => {
139 for asn in asns {
140 parts.push(asn.to_string());
141 }
142 }
143 AsPathSegment::AsSet(asns) => {
144 let inner: Vec<String> = asns.iter().map(ToString::to_string).collect();
145 parts.push(format!("{{{}}}", inner.join(" ")));
146 }
147 }
148 }
149 parts.join(" ")
150 }
151}
152
153#[must_use]
159pub fn is_private_asn(asn: u32) -> bool {
160 (64512..=65534).contains(&asn) || (4_200_000_000..=4_294_967_294).contains(&asn)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub struct MpReachNlri {
169 pub afi: Afi,
171 pub safi: Safi,
173 pub next_hop: IpAddr,
181 pub link_local_next_hop: Option<Ipv6Addr>,
186 pub announced: Vec<NlriEntry>,
188 pub flowspec_announced: Vec<crate::flowspec::FlowSpecRule>,
190 pub evpn_announced: Vec<crate::evpn::EvpnRoute>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub struct MpUnreachNlri {
200 pub afi: Afi,
202 pub safi: Safi,
204 pub withdrawn: Vec<NlriEntry>,
206 pub flowspec_withdrawn: Vec<crate::flowspec::FlowSpecRule>,
208 pub evpn_withdrawn: Vec<crate::evpn::EvpnRoute>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub struct ExtendedCommunity(u64);
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct DfElectionExtendedCommunity {
222 pub algorithm_id: u8,
224 pub capabilities: u16,
226 pub preference: Option<u16>,
229}
230
231impl ExtendedCommunity {
232 #[must_use]
234 pub fn new(raw: u64) -> Self {
235 Self(raw)
236 }
237
238 #[must_use]
240 pub fn as_u64(self) -> u64 {
241 self.0
242 }
243
244 #[must_use]
246 pub fn type_byte(self) -> u8 {
247 (self.0 >> 56) as u8
248 }
249
250 #[must_use]
252 pub fn subtype(self) -> u8 {
253 self.0.to_be_bytes()[1]
254 }
255
256 #[must_use]
258 pub fn is_transitive(self) -> bool {
259 self.type_byte() & 0x40 == 0
260 }
261
262 #[must_use]
264 pub fn value_bytes(self) -> [u8; 6] {
265 let b = self.0.to_be_bytes();
266 [b[2], b[3], b[4], b[5], b[6], b[7]]
267 }
268
269 #[must_use]
280 pub fn route_target(self) -> Option<(u32, u32)> {
281 if self.subtype() != 0x02 {
282 return None;
283 }
284 self.decode_two_part()
285 }
286
287 #[must_use]
294 pub fn route_origin(self) -> Option<(u32, u32)> {
295 if self.subtype() != 0x03 {
296 return None;
297 }
298 self.decode_two_part()
299 }
300
301 #[must_use]
319 pub fn as_bgp_encapsulation(self) -> Option<u16> {
320 if self.type_byte() & 0x3F != 0x03 || self.subtype() != 0x0C {
321 return None;
322 }
323 let v = self.value_bytes();
324 Some(u16::from_be_bytes([v[4], v[5]]))
325 }
326
327 #[must_use]
331 pub fn bgp_encapsulation(tunnel_type: u16) -> Self {
332 let tt = tunnel_type.to_be_bytes();
333 let raw = u64::from_be_bytes([0x03, 0x0C, 0, 0, 0, 0, tt[0], tt[1]]);
334 Self(raw)
335 }
336
337 #[must_use]
344 pub fn as_mac_mobility(self) -> Option<(bool, u32)> {
345 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x00 {
346 return None;
347 }
348 let v = self.value_bytes();
349 let sticky = (v[0] & 0x01) != 0;
350 let seq = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
351 Some((sticky, seq))
352 }
353
354 #[must_use]
356 pub fn mac_mobility(sticky: bool, sequence: u32) -> Self {
357 let flags = u8::from(sticky);
358 let s = sequence.to_be_bytes();
359 let raw = u64::from_be_bytes([0x06, 0x00, flags, 0, s[0], s[1], s[2], s[3]]);
360 Self(raw)
361 }
362
363 #[must_use]
369 pub fn as_esi_label(self) -> Option<(bool, u32)> {
370 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x01 {
371 return None;
372 }
373 let v = self.value_bytes();
374 let single_active = (v[0] & 0x01) != 0;
375 let label = (u32::from(v[3]) << 16) | (u32::from(v[4]) << 8) | u32::from(v[5]);
376 Some((single_active, label))
377 }
378
379 #[must_use]
383 pub fn esi_label(single_active: bool, label: u32) -> Self {
384 let flags = u8::from(single_active);
385 let l = label & 0x00FF_FFFF;
386 #[expect(clippy::cast_possible_truncation)]
387 let raw = u64::from_be_bytes([
388 0x06,
389 0x01,
390 flags,
391 0,
392 0,
393 (l >> 16) as u8,
394 (l >> 8) as u8,
395 l as u8,
396 ]);
397 Self(raw)
398 }
399
400 #[must_use]
406 pub fn as_es_import_rt(self) -> Option<[u8; 6]> {
407 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x02 {
408 return None;
409 }
410 Some(self.value_bytes())
411 }
412
413 #[must_use]
415 pub fn es_import_rt(mac: [u8; 6]) -> Self {
416 let raw = u64::from_be_bytes([0x06, 0x02, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
417 Self(raw)
418 }
419
420 #[must_use]
423 pub fn as_df_election(self) -> Option<DfElectionExtendedCommunity> {
424 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x06 {
425 return None;
426 }
427 let v = self.value_bytes();
428 let algorithm_id = v[0] & 0x1f;
429 let capabilities = u16::from_be_bytes([v[1], v[2]]);
430 let preference = match algorithm_id {
431 2 | 3 => Some(u16::from_be_bytes([v[4], v[5]])),
432 _ => None,
433 };
434 Some(DfElectionExtendedCommunity {
435 algorithm_id,
436 capabilities,
437 preference,
438 })
439 }
440
441 #[must_use]
448 pub fn df_election(algorithm_id: u8, capabilities: u16, preference: Option<u16>) -> Self {
449 let alg = algorithm_id & 0x1f;
450 let cap = capabilities.to_be_bytes();
451 let pref = preference.unwrap_or(0).to_be_bytes();
452 let raw = u64::from_be_bytes([0x06, 0x06, alg, cap[0], cap[1], 0, pref[0], pref[1]]);
453 Self(raw)
454 }
455
456 #[must_use]
461 pub fn as_router_mac(self) -> Option<[u8; 6]> {
462 if self.type_byte() & 0x3F != 0x06 || self.subtype() != 0x03 {
463 return None;
464 }
465 Some(self.value_bytes())
466 }
467
468 #[must_use]
470 pub fn router_mac(mac: [u8; 6]) -> Self {
471 let raw = u64::from_be_bytes([0x06, 0x03, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]]);
472 Self(raw)
473 }
474
475 #[must_use]
482 pub fn as_default_gateway(self) -> bool {
483 self.type_byte() & 0x3F == 0x03 && self.subtype() == 0x0D && self.value_bytes() == [0u8; 6]
484 }
485
486 #[must_use]
488 pub fn default_gateway() -> Self {
489 let raw = u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0]);
490 Self(raw)
491 }
492
493 fn decode_two_part(self) -> Option<(u32, u32)> {
499 let v = self.value_bytes();
500 let t = self.type_byte() & 0x3F; match t {
502 0x00 => {
504 let global = u32::from(u16::from_be_bytes([v[0], v[1]]));
505 let local = u32::from_be_bytes([v[2], v[3], v[4], v[5]]);
506 Some((global, local))
507 }
508 0x01 | 0x02 => {
510 let global = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
511 let local = u32::from(u16::from_be_bytes([v[4], v[5]]));
512 Some((global, local))
513 }
514 _ => None,
515 }
516 }
517}
518
519impl fmt::Display for ExtendedCommunity {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 let is_ipv4 = self.type_byte() & 0x3F == 0x01;
522 if let Some((g, l)) = self.route_target() {
523 if is_ipv4 {
524 write!(f, "RT:{}:{l}", Ipv4Addr::from(g))
525 } else {
526 write!(f, "RT:{g}:{l}")
527 }
528 } else if let Some((g, l)) = self.route_origin() {
529 if is_ipv4 {
530 write!(f, "RO:{}:{l}", Ipv4Addr::from(g))
531 } else {
532 write!(f, "RO:{g}:{l}")
533 }
534 } else {
535 write!(f, "0x{:016x}", self.0)
536 }
537 }
538}
539
540#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
544pub struct LargeCommunity {
545 pub global_admin: u32,
547 pub local_data1: u32,
549 pub local_data2: u32,
551}
552
553impl LargeCommunity {
554 #[must_use]
556 pub fn new(global_admin: u32, local_data1: u32, local_data2: u32) -> Self {
557 Self {
558 global_admin,
559 local_data1,
560 local_data2,
561 }
562 }
563}
564
565impl fmt::Display for LargeCommunity {
566 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567 write!(
568 f,
569 "{}:{}:{}",
570 self.global_admin, self.local_data1, self.local_data2
571 )
572 }
573}
574
575#[derive(Debug, Clone, PartialEq, Eq, Hash)]
580pub enum PathAttribute {
581 Origin(Origin),
583 AsPath(AsPath),
585 NextHop(Ipv4Addr),
587 LocalPref(u32),
589 Med(u32),
591 Communities(Vec<u32>),
593 ExtendedCommunities(Vec<ExtendedCommunity>),
595 LargeCommunities(Vec<LargeCommunity>),
597 OriginatorId(Ipv4Addr),
599 ClusterList(Vec<Ipv4Addr>),
601 MpReachNlri(MpReachNlri),
603 MpUnreachNlri(MpUnreachNlri),
605 PmsiTunnel(crate::pmsi::PmsiTunnel),
608 Unknown(RawAttribute),
610}
611
612impl PathAttribute {
613 #[must_use]
615 pub fn type_code(&self) -> u8 {
616 match self {
617 Self::Origin(_) => attr_type::ORIGIN,
618 Self::AsPath(_) => attr_type::AS_PATH,
619 Self::NextHop(_) => attr_type::NEXT_HOP,
620 Self::LocalPref(_) => attr_type::LOCAL_PREF,
621 Self::Med(_) => attr_type::MULTI_EXIT_DISC,
622 Self::Communities(_) => attr_type::COMMUNITIES,
623 Self::OriginatorId(_) => attr_type::ORIGINATOR_ID,
624 Self::ClusterList(_) => attr_type::CLUSTER_LIST,
625 Self::ExtendedCommunities(_) => attr_type::EXTENDED_COMMUNITIES,
626 Self::LargeCommunities(_) => attr_type::LARGE_COMMUNITIES,
627 Self::MpReachNlri(_) => attr_type::MP_REACH_NLRI,
628 Self::MpUnreachNlri(_) => attr_type::MP_UNREACH_NLRI,
629 Self::PmsiTunnel(_) => attr_type::PMSI_TUNNEL,
630 Self::Unknown(raw) => raw.type_code,
631 }
632 }
633
634 #[must_use]
636 pub fn flags(&self) -> u8 {
637 match self {
638 Self::Origin(_) | Self::AsPath(_) | Self::NextHop(_) | Self::LocalPref(_) => {
639 attr_flags::TRANSITIVE
640 }
641 Self::Med(_)
642 | Self::OriginatorId(_)
643 | Self::ClusterList(_)
644 | Self::MpReachNlri(_)
645 | Self::MpUnreachNlri(_) => attr_flags::OPTIONAL,
646 Self::Communities(_)
647 | Self::ExtendedCommunities(_)
648 | Self::LargeCommunities(_)
649 | Self::PmsiTunnel(_) => attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
650 Self::Unknown(raw) => raw.flags,
651 }
652 }
653}
654
655#[derive(Debug, Clone, PartialEq, Eq, Hash)]
660pub struct RawAttribute {
661 pub flags: u8,
663 pub type_code: u8,
665 pub data: Bytes,
667}
668
669pub fn decode_path_attributes(
680 mut buf: &[u8],
681 four_octet_as: bool,
682 add_path_families: &[(Afi, Safi)],
683) -> Result<Vec<PathAttribute>, DecodeError> {
684 let mut attrs = Vec::new();
685
686 while !buf.is_empty() {
687 if buf.len() < 2 {
689 return Err(DecodeError::MalformedField {
690 message_type: "UPDATE",
691 detail: "truncated attribute header".to_string(),
692 });
693 }
694
695 let flags = buf[0];
696 let type_code = buf[1];
697 buf = &buf[2..];
698
699 let extended = (flags & attr_flags::EXTENDED_LENGTH) != 0;
700 let value_len = if extended {
701 if buf.len() < 2 {
702 return Err(DecodeError::MalformedField {
703 message_type: "UPDATE",
704 detail: "truncated extended-length attribute".to_string(),
705 });
706 }
707 let len = u16::from_be_bytes([buf[0], buf[1]]) as usize;
708 buf = &buf[2..];
709 len
710 } else {
711 if buf.is_empty() {
712 return Err(DecodeError::MalformedField {
713 message_type: "UPDATE",
714 detail: "truncated attribute length".to_string(),
715 });
716 }
717 let len = buf[0] as usize;
718 buf = &buf[1..];
719 len
720 };
721
722 if buf.len() < value_len {
723 return Err(DecodeError::MalformedField {
724 message_type: "UPDATE",
725 detail: format!(
726 "attribute type {type_code} value truncated: need {value_len}, have {}",
727 buf.len()
728 ),
729 });
730 }
731
732 let value = &buf[..value_len];
733 buf = &buf[value_len..];
734
735 let attr =
736 decode_attribute_value(flags, type_code, value, four_octet_as, add_path_families)?;
737 attrs.push(attr);
738 }
739
740 Ok(attrs)
741}
742
743#[expect(clippy::too_many_lines)]
745fn decode_attribute_value(
746 flags: u8,
747 type_code: u8,
748 value: &[u8],
749 four_octet_as: bool,
750 add_path_families: &[(Afi, Safi)],
751) -> Result<PathAttribute, DecodeError> {
752 let flags_mask = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
754 if let Some(expected) = expected_flags(type_code)
755 && (flags & flags_mask) != expected
756 {
757 return Err(DecodeError::UpdateAttributeError {
758 subcode: update_subcode::ATTRIBUTE_FLAGS_ERROR,
759 data: attr_error_data(flags, type_code, value),
760 detail: format!(
761 "type {} flags {:#04x} (expected {:#04x})",
762 type_code,
763 flags & flags_mask,
764 expected
765 ),
766 });
767 }
768
769 match type_code {
770 attr_type::ORIGIN => {
771 if value.len() != 1 {
772 return Err(DecodeError::UpdateAttributeError {
773 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
774 data: attr_error_data(flags, type_code, value),
775 detail: format!("ORIGIN length {} (expected 1)", value.len()),
776 });
777 }
778 match Origin::from_u8(value[0]) {
779 Some(origin) => Ok(PathAttribute::Origin(origin)),
780 None => Err(DecodeError::UpdateAttributeError {
781 subcode: update_subcode::INVALID_ORIGIN,
782 data: attr_error_data(flags, type_code, value),
783 detail: format!("invalid ORIGIN value {}", value[0]),
784 }),
785 }
786 }
787
788 attr_type::AS_PATH => {
789 let segments = decode_as_path(value, four_octet_as).map_err(|e| {
790 DecodeError::UpdateAttributeError {
791 subcode: update_subcode::MALFORMED_AS_PATH,
792 data: attr_error_data(flags, type_code, value),
793 detail: e.to_string(),
794 }
795 })?;
796 Ok(PathAttribute::AsPath(AsPath { segments }))
797 }
798
799 attr_type::NEXT_HOP => {
800 if value.len() != 4 {
801 return Err(DecodeError::UpdateAttributeError {
802 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
803 data: attr_error_data(flags, type_code, value),
804 detail: format!("NEXT_HOP length {} (expected 4)", value.len()),
805 });
806 }
807 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
808 Ok(PathAttribute::NextHop(addr))
809 }
810
811 attr_type::MULTI_EXIT_DISC => {
812 if value.len() != 4 {
813 return Err(DecodeError::UpdateAttributeError {
814 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
815 data: attr_error_data(flags, type_code, value),
816 detail: format!("MED length {} (expected 4)", value.len()),
817 });
818 }
819 let med = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
820 Ok(PathAttribute::Med(med))
821 }
822
823 attr_type::LOCAL_PREF => {
824 if value.len() != 4 {
825 return Err(DecodeError::UpdateAttributeError {
826 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
827 data: attr_error_data(flags, type_code, value),
828 detail: format!("LOCAL_PREF length {} (expected 4)", value.len()),
829 });
830 }
831 let lp = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
832 Ok(PathAttribute::LocalPref(lp))
833 }
834
835 attr_type::COMMUNITIES => {
836 if !value.len().is_multiple_of(4) {
837 return Err(DecodeError::UpdateAttributeError {
838 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
839 data: attr_error_data(flags, type_code, value),
840 detail: format!("COMMUNITIES length {} not a multiple of 4", value.len()),
841 });
842 }
843 let communities = value
844 .chunks_exact(4)
845 .map(|c| u32::from_be_bytes([c[0], c[1], c[2], c[3]]))
846 .collect();
847 Ok(PathAttribute::Communities(communities))
848 }
849
850 attr_type::EXTENDED_COMMUNITIES => {
851 if !value.len().is_multiple_of(8) {
852 return Err(DecodeError::UpdateAttributeError {
853 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
854 data: attr_error_data(flags, type_code, value),
855 detail: format!(
856 "EXTENDED_COMMUNITIES length {} not a multiple of 8",
857 value.len()
858 ),
859 });
860 }
861 let communities = value
862 .chunks_exact(8)
863 .map(|c| {
864 ExtendedCommunity::new(u64::from_be_bytes([
865 c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
866 ]))
867 })
868 .collect();
869 Ok(PathAttribute::ExtendedCommunities(communities))
870 }
871
872 attr_type::ORIGINATOR_ID => {
873 if value.len() != 4 {
874 return Err(DecodeError::UpdateAttributeError {
875 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
876 data: attr_error_data(flags, type_code, value),
877 detail: format!("ORIGINATOR_ID length {} (expected 4)", value.len()),
878 });
879 }
880 let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
881 Ok(PathAttribute::OriginatorId(addr))
882 }
883
884 attr_type::CLUSTER_LIST => {
885 if !value.len().is_multiple_of(4) {
886 return Err(DecodeError::UpdateAttributeError {
887 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
888 data: attr_error_data(flags, type_code, value),
889 detail: format!("CLUSTER_LIST length {} not a multiple of 4", value.len()),
890 });
891 }
892 let ids = value
893 .chunks_exact(4)
894 .map(|c| Ipv4Addr::new(c[0], c[1], c[2], c[3]))
895 .collect();
896 Ok(PathAttribute::ClusterList(ids))
897 }
898
899 attr_type::LARGE_COMMUNITIES => {
900 if value.is_empty() || !value.len().is_multiple_of(12) {
901 return Err(DecodeError::UpdateAttributeError {
902 subcode: update_subcode::ATTRIBUTE_LENGTH_ERROR,
903 data: attr_error_data(flags, type_code, value),
904 detail: format!(
905 "LARGE_COMMUNITIES length {} invalid (must be non-zero multiple of 12)",
906 value.len()
907 ),
908 });
909 }
910 let communities = value
911 .chunks_exact(12)
912 .map(|c| {
913 LargeCommunity::new(
914 u32::from_be_bytes([c[0], c[1], c[2], c[3]]),
915 u32::from_be_bytes([c[4], c[5], c[6], c[7]]),
916 u32::from_be_bytes([c[8], c[9], c[10], c[11]]),
917 )
918 })
919 .collect();
920 Ok(PathAttribute::LargeCommunities(communities))
921 }
922
923 attr_type::MP_REACH_NLRI => decode_mp_reach_nlri(value, add_path_families),
924 attr_type::MP_UNREACH_NLRI => decode_mp_unreach_nlri(value, add_path_families),
925
926 attr_type::PMSI_TUNNEL => {
927 let pmsi = crate::pmsi::PmsiTunnel::decode(value)?;
928 Ok(PathAttribute::PmsiTunnel(pmsi))
929 }
930
931 _ => Ok(PathAttribute::Unknown(RawAttribute {
933 flags,
934 type_code,
935 data: Bytes::copy_from_slice(value),
936 })),
937 }
938}
939
940#[expect(clippy::too_many_lines)]
945fn decode_mp_reach_nlri(
946 value: &[u8],
947 add_path_families: &[(Afi, Safi)],
948) -> Result<PathAttribute, DecodeError> {
949 if value.len() < 5 {
950 return Err(DecodeError::MalformedField {
951 message_type: "UPDATE",
952 detail: format!("MP_REACH_NLRI too short: {} bytes", value.len()),
953 });
954 }
955
956 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
957 let safi_raw = value[2];
958 let nh_len = value[3] as usize;
959
960 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
961 message_type: "UPDATE",
962 detail: format!("MP_REACH_NLRI unsupported AFI {afi_raw}"),
963 })?;
964 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
965 message_type: "UPDATE",
966 detail: format!("MP_REACH_NLRI unsupported SAFI {safi_raw}"),
967 })?;
968
969 if value.len() < 4 + nh_len + 1 {
971 return Err(DecodeError::MalformedField {
972 message_type: "UPDATE",
973 detail: format!(
974 "MP_REACH_NLRI truncated: NH-Len={nh_len}, have {} bytes total",
975 value.len()
976 ),
977 });
978 }
979
980 let nh_bytes = &value[4..4 + nh_len];
981 let mut link_local_next_hop: Option<Ipv6Addr> = None;
983 let next_hop = if safi == Safi::FlowSpec {
984 if nh_len != 0 {
985 return Err(DecodeError::MalformedField {
986 message_type: "UPDATE",
987 detail: format!("MP_REACH_NLRI FlowSpec next-hop length {nh_len} (expected 0)"),
988 });
989 }
990 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
991 } else {
992 match afi {
993 Afi::Ipv4 => match nh_len {
994 4 => IpAddr::V4(Ipv4Addr::new(
995 nh_bytes[0],
996 nh_bytes[1],
997 nh_bytes[2],
998 nh_bytes[3],
999 )),
1000 16 | 32 => {
1001 let mut octets = [0u8; 16];
1002 octets.copy_from_slice(&nh_bytes[..16]);
1003 if nh_len == 32 {
1004 let mut ll = [0u8; 16];
1005 ll.copy_from_slice(&nh_bytes[16..32]);
1006 link_local_next_hop = Some(Ipv6Addr::from(ll));
1007 }
1008 IpAddr::V6(Ipv6Addr::from(octets))
1009 }
1010 _ => {
1011 return Err(DecodeError::MalformedField {
1012 message_type: "UPDATE",
1013 detail: format!(
1014 "MP_REACH_NLRI IPv4 next-hop length {nh_len} (expected 4, 16, or 32)"
1015 ),
1016 });
1017 }
1018 },
1019 Afi::Ipv6 => {
1020 if nh_len != 16 && nh_len != 32 {
1021 return Err(DecodeError::MalformedField {
1022 message_type: "UPDATE",
1023 detail: format!(
1024 "MP_REACH_NLRI IPv6 next-hop length {nh_len} (expected 16 or 32)"
1025 ),
1026 });
1027 }
1028 let mut octets = [0u8; 16];
1029 octets.copy_from_slice(&nh_bytes[..16]);
1030 if nh_len == 32 {
1031 let mut ll = [0u8; 16];
1032 ll.copy_from_slice(&nh_bytes[16..32]);
1033 link_local_next_hop = Some(Ipv6Addr::from(ll));
1034 }
1035 IpAddr::V6(Ipv6Addr::from(octets))
1036 }
1037 Afi::L2Vpn => match nh_len {
1038 4 => IpAddr::V4(Ipv4Addr::new(
1039 nh_bytes[0],
1040 nh_bytes[1],
1041 nh_bytes[2],
1042 nh_bytes[3],
1043 )),
1044 16 => {
1045 let mut octets = [0u8; 16];
1046 octets.copy_from_slice(&nh_bytes[..16]);
1047 IpAddr::V6(Ipv6Addr::from(octets))
1048 }
1049 _ => {
1050 return Err(DecodeError::MalformedField {
1051 message_type: "UPDATE",
1052 detail: format!(
1053 "MP_REACH_NLRI L2VPN next-hop length {nh_len} (expected 4 or 16)"
1054 ),
1055 });
1056 }
1057 },
1058 }
1059 };
1060
1061 let nlri_start = 4 + nh_len + 1;
1063 let nlri_bytes = &value[nlri_start..];
1064
1065 if safi == Safi::FlowSpec {
1067 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(nlri_bytes, afi)?;
1068 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1069 afi,
1070 safi,
1071 next_hop,
1072 link_local_next_hop,
1073 announced: vec![],
1074 flowspec_announced: flowspec_rules,
1075 evpn_announced: vec![],
1076 }));
1077 }
1078
1079 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1081 let routes = crate::evpn::decode_evpn_nlri(nlri_bytes)?;
1082 return Ok(PathAttribute::MpReachNlri(MpReachNlri {
1083 afi,
1084 safi,
1085 next_hop,
1086 link_local_next_hop,
1087 announced: vec![],
1088 flowspec_announced: vec![],
1089 evpn_announced: routes,
1090 }));
1091 }
1092
1093 if safi == Safi::Evpn {
1097 return Err(DecodeError::MalformedField {
1098 message_type: "UPDATE",
1099 detail: format!(
1100 "MP_REACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1101 afi as u16
1102 ),
1103 });
1104 }
1105
1106 let add_path = add_path_families.contains(&(afi, safi));
1107 let announced = match (afi, add_path) {
1108 (Afi::Ipv4, false) => crate::nlri::decode_nlri(nlri_bytes)?
1109 .into_iter()
1110 .map(|p| NlriEntry {
1111 path_id: 0,
1112 prefix: Prefix::V4(p),
1113 })
1114 .collect(),
1115 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(nlri_bytes)?
1116 .into_iter()
1117 .map(|e| NlriEntry {
1118 path_id: e.path_id,
1119 prefix: Prefix::V4(e.prefix),
1120 })
1121 .collect(),
1122 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(nlri_bytes)?
1123 .into_iter()
1124 .map(|p| NlriEntry {
1125 path_id: 0,
1126 prefix: Prefix::V6(p),
1127 })
1128 .collect(),
1129 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(nlri_bytes)?,
1130 (Afi::L2Vpn, _) => {
1131 return Err(DecodeError::MalformedField {
1132 message_type: "UPDATE",
1133 detail: format!(
1134 "MP_REACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1135 safi as u8
1136 ),
1137 });
1138 }
1139 };
1140
1141 Ok(PathAttribute::MpReachNlri(MpReachNlri {
1142 afi,
1143 safi,
1144 next_hop,
1145 link_local_next_hop,
1146 announced,
1147 flowspec_announced: vec![],
1148 evpn_announced: vec![],
1149 }))
1150}
1151
1152fn decode_mp_unreach_nlri(
1157 value: &[u8],
1158 add_path_families: &[(Afi, Safi)],
1159) -> Result<PathAttribute, DecodeError> {
1160 if value.len() < 3 {
1161 return Err(DecodeError::MalformedField {
1162 message_type: "UPDATE",
1163 detail: format!("MP_UNREACH_NLRI too short: {} bytes", value.len()),
1164 });
1165 }
1166
1167 let afi_raw = u16::from_be_bytes([value[0], value[1]]);
1168 let safi_raw = value[2];
1169
1170 let afi = Afi::from_u16(afi_raw).ok_or_else(|| DecodeError::MalformedField {
1171 message_type: "UPDATE",
1172 detail: format!("MP_UNREACH_NLRI unsupported AFI {afi_raw}"),
1173 })?;
1174 let safi = Safi::from_u8(safi_raw).ok_or_else(|| DecodeError::MalformedField {
1175 message_type: "UPDATE",
1176 detail: format!("MP_UNREACH_NLRI unsupported SAFI {safi_raw}"),
1177 })?;
1178
1179 let withdrawn_bytes = &value[3..];
1180
1181 if safi == Safi::FlowSpec {
1183 let flowspec_rules = crate::flowspec::decode_flowspec_nlri(withdrawn_bytes, afi)?;
1184 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1185 afi,
1186 safi,
1187 withdrawn: vec![],
1188 flowspec_withdrawn: flowspec_rules,
1189 evpn_withdrawn: vec![],
1190 }));
1191 }
1192
1193 if afi == Afi::L2Vpn && safi == Safi::Evpn {
1195 let routes = crate::evpn::decode_evpn_nlri(withdrawn_bytes)?;
1196 return Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1197 afi,
1198 safi,
1199 withdrawn: vec![],
1200 flowspec_withdrawn: vec![],
1201 evpn_withdrawn: routes,
1202 }));
1203 }
1204
1205 if safi == Safi::Evpn {
1209 return Err(DecodeError::MalformedField {
1210 message_type: "UPDATE",
1211 detail: format!(
1212 "MP_UNREACH_NLRI SAFI EVPN with non-L2VPN AFI {} (only AFI L2VPN supported)",
1213 afi as u16
1214 ),
1215 });
1216 }
1217
1218 let add_path = add_path_families.contains(&(afi, safi));
1219 let withdrawn = match (afi, add_path) {
1220 (Afi::Ipv4, false) => crate::nlri::decode_nlri(withdrawn_bytes)?
1221 .into_iter()
1222 .map(|p| NlriEntry {
1223 path_id: 0,
1224 prefix: Prefix::V4(p),
1225 })
1226 .collect(),
1227 (Afi::Ipv4, true) => crate::nlri::decode_nlri_addpath(withdrawn_bytes)?
1228 .into_iter()
1229 .map(|e| NlriEntry {
1230 path_id: e.path_id,
1231 prefix: Prefix::V4(e.prefix),
1232 })
1233 .collect(),
1234 (Afi::Ipv6, false) => crate::nlri::decode_ipv6_nlri(withdrawn_bytes)?
1235 .into_iter()
1236 .map(|p| NlriEntry {
1237 path_id: 0,
1238 prefix: Prefix::V6(p),
1239 })
1240 .collect(),
1241 (Afi::Ipv6, true) => crate::nlri::decode_ipv6_nlri_addpath(withdrawn_bytes)?,
1242 (Afi::L2Vpn, _) => {
1243 return Err(DecodeError::MalformedField {
1244 message_type: "UPDATE",
1245 detail: format!(
1246 "MP_UNREACH_NLRI L2VPN with unsupported SAFI {} (only EVPN supported)",
1247 safi as u8
1248 ),
1249 });
1250 }
1251 };
1252
1253 Ok(PathAttribute::MpUnreachNlri(MpUnreachNlri {
1254 afi,
1255 safi,
1256 withdrawn,
1257 flowspec_withdrawn: vec![],
1258 evpn_withdrawn: vec![],
1259 }))
1260}
1261
1262fn decode_as_path(mut buf: &[u8], four_octet_as: bool) -> Result<Vec<AsPathSegment>, DecodeError> {
1264 let as_size: usize = if four_octet_as { 4 } else { 2 };
1265 let mut segments = Vec::new();
1266
1267 while !buf.is_empty() {
1268 if buf.len() < 2 {
1269 return Err(DecodeError::MalformedField {
1270 message_type: "UPDATE",
1271 detail: "truncated AS_PATH segment header".to_string(),
1272 });
1273 }
1274
1275 let seg_type = buf[0];
1276 let seg_count = buf[1] as usize;
1277 buf = &buf[2..];
1278
1279 let needed = seg_count * as_size;
1280 if buf.len() < needed {
1281 return Err(DecodeError::MalformedField {
1282 message_type: "UPDATE",
1283 detail: format!(
1284 "AS_PATH segment truncated: need {needed} bytes for {seg_count} ASNs, have {}",
1285 buf.len()
1286 ),
1287 });
1288 }
1289
1290 let mut asns = Vec::with_capacity(seg_count);
1291 for _ in 0..seg_count {
1292 let asn = if four_octet_as {
1293 let v = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
1294 buf = &buf[4..];
1295 v
1296 } else {
1297 let v = u32::from(u16::from_be_bytes([buf[0], buf[1]]));
1298 buf = &buf[2..];
1299 v
1300 };
1301 asns.push(asn);
1302 }
1303
1304 match seg_type {
1305 as_path_segment::AS_SET => segments.push(AsPathSegment::AsSet(asns)),
1306 as_path_segment::AS_SEQUENCE => segments.push(AsPathSegment::AsSequence(asns)),
1307 _ => {
1308 return Err(DecodeError::MalformedField {
1309 message_type: "UPDATE",
1310 detail: format!("unknown AS_PATH segment type {seg_type}"),
1311 });
1312 }
1313 }
1314 }
1315
1316 Ok(segments)
1317}
1318
1319pub(crate) fn attr_error_data(flags: u8, type_code: u8, value: &[u8]) -> Vec<u8> {
1322 let mut buf = Vec::with_capacity(3 + value.len());
1323 if value.len() > 255 {
1324 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1325 buf.push(type_code);
1326 #[expect(clippy::cast_possible_truncation)]
1327 let len = value.len() as u16;
1328 buf.extend_from_slice(&len.to_be_bytes());
1329 } else {
1330 buf.push(flags);
1331 buf.push(type_code);
1332 #[expect(clippy::cast_possible_truncation)]
1333 buf.push(value.len() as u8);
1334 }
1335 buf.extend_from_slice(value);
1336 buf
1337}
1338
1339fn expected_flags(type_code: u8) -> Option<u8> {
1342 match type_code {
1343 attr_type::ORIGIN
1345 | attr_type::AS_PATH
1346 | attr_type::NEXT_HOP
1347 | attr_type::LOCAL_PREF
1348 | attr_type::ATOMIC_AGGREGATE => Some(attr_flags::TRANSITIVE),
1349 attr_type::MULTI_EXIT_DISC
1352 | attr_type::ORIGINATOR_ID
1353 | attr_type::CLUSTER_LIST
1354 | attr_type::MP_REACH_NLRI
1355 | attr_type::MP_UNREACH_NLRI => Some(attr_flags::OPTIONAL),
1356 attr_type::AGGREGATOR
1358 | attr_type::COMMUNITIES
1359 | attr_type::EXTENDED_COMMUNITIES
1360 | attr_type::LARGE_COMMUNITIES
1361 | attr_type::PMSI_TUNNEL => Some(attr_flags::OPTIONAL | attr_flags::TRANSITIVE),
1362 _ => None,
1363 }
1364}
1365
1366#[expect(
1374 clippy::too_many_lines,
1375 reason = "dispatch arms are inherently O(variants); each new path attribute adds a small block"
1376)]
1377pub fn encode_path_attributes(
1378 attrs: &[PathAttribute],
1379 buf: &mut Vec<u8>,
1380 four_octet_as: bool,
1381 add_path_mp: bool,
1382) {
1383 for attr in attrs {
1384 let mut value = Vec::new();
1385 let flags;
1386 let type_code;
1387
1388 match attr {
1389 PathAttribute::Origin(origin) => {
1390 flags = attr_flags::TRANSITIVE;
1391 type_code = attr_type::ORIGIN;
1392 value.push(*origin as u8);
1393 }
1394 PathAttribute::AsPath(as_path) => {
1395 flags = attr_flags::TRANSITIVE;
1396 type_code = attr_type::AS_PATH;
1397 encode_as_path(as_path, &mut value, four_octet_as);
1398 }
1399 PathAttribute::NextHop(addr) => {
1400 flags = attr_flags::TRANSITIVE;
1401 type_code = attr_type::NEXT_HOP;
1402 value.extend_from_slice(&addr.octets());
1403 }
1404 PathAttribute::Med(med) => {
1405 flags = attr_flags::OPTIONAL;
1406 type_code = attr_type::MULTI_EXIT_DISC;
1407 value.extend_from_slice(&med.to_be_bytes());
1408 }
1409 PathAttribute::LocalPref(lp) => {
1410 flags = attr_flags::TRANSITIVE;
1411 type_code = attr_type::LOCAL_PREF;
1412 value.extend_from_slice(&lp.to_be_bytes());
1413 }
1414 PathAttribute::Communities(communities) => {
1415 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1416 type_code = attr_type::COMMUNITIES;
1417 for &c in communities {
1418 value.extend_from_slice(&c.to_be_bytes());
1419 }
1420 }
1421 PathAttribute::ExtendedCommunities(communities) => {
1422 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1423 type_code = attr_type::EXTENDED_COMMUNITIES;
1424 for &c in communities {
1425 value.extend_from_slice(&c.as_u64().to_be_bytes());
1426 }
1427 }
1428 PathAttribute::LargeCommunities(communities) => {
1429 flags = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1430 type_code = attr_type::LARGE_COMMUNITIES;
1431 for &c in communities {
1432 value.extend_from_slice(&c.global_admin.to_be_bytes());
1433 value.extend_from_slice(&c.local_data1.to_be_bytes());
1434 value.extend_from_slice(&c.local_data2.to_be_bytes());
1435 }
1436 }
1437 PathAttribute::OriginatorId(addr) => {
1438 flags = attr_flags::OPTIONAL;
1439 type_code = attr_type::ORIGINATOR_ID;
1440 value.extend_from_slice(&addr.octets());
1441 }
1442 PathAttribute::ClusterList(ids) => {
1443 flags = attr_flags::OPTIONAL;
1444 type_code = attr_type::CLUSTER_LIST;
1445 for id in ids {
1446 value.extend_from_slice(&id.octets());
1447 }
1448 }
1449 PathAttribute::MpReachNlri(mp) => {
1450 flags = attr_flags::OPTIONAL;
1451 type_code = attr_type::MP_REACH_NLRI;
1452 encode_mp_reach_nlri(mp, &mut value, add_path_mp);
1453 }
1454 PathAttribute::MpUnreachNlri(mp) => {
1455 flags = attr_flags::OPTIONAL;
1456 type_code = attr_type::MP_UNREACH_NLRI;
1457 encode_mp_unreach_nlri(mp, &mut value, add_path_mp);
1458 }
1459 PathAttribute::PmsiTunnel(pmsi) => {
1460 (flags, type_code) = (
1462 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
1463 attr_type::PMSI_TUNNEL,
1464 );
1465 pmsi.encode(&mut value);
1466 }
1467 PathAttribute::Unknown(raw) => {
1468 let optional_transitive = attr_flags::OPTIONAL | attr_flags::TRANSITIVE;
1472 flags = if (raw.flags & optional_transitive) == optional_transitive {
1473 raw.flags | attr_flags::PARTIAL
1474 } else {
1475 raw.flags
1476 };
1477 type_code = raw.type_code;
1478 value.extend_from_slice(&raw.data);
1479 }
1480 }
1481
1482 if value.len() > 255 {
1484 buf.push(flags | attr_flags::EXTENDED_LENGTH);
1485 buf.push(type_code);
1486 #[expect(clippy::cast_possible_truncation)]
1487 let len = value.len() as u16;
1488 buf.extend_from_slice(&len.to_be_bytes());
1489 } else {
1490 buf.push(flags);
1491 buf.push(type_code);
1492 #[expect(clippy::cast_possible_truncation)]
1493 buf.push(value.len() as u8);
1494 }
1495 buf.extend_from_slice(&value);
1496 }
1497}
1498
1499fn encode_mp_reach_nlri(mp: &MpReachNlri, buf: &mut Vec<u8>, add_path: bool) {
1504 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1505 buf.push(mp.safi as u8);
1506
1507 if mp.safi == Safi::FlowSpec {
1509 buf.push(0); buf.push(0); crate::flowspec::encode_flowspec_nlri(&mp.flowspec_announced, buf, mp.afi);
1512 return;
1513 }
1514
1515 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1517 match mp.next_hop {
1518 IpAddr::V4(addr) => {
1519 buf.push(4);
1520 buf.extend_from_slice(&addr.octets());
1521 }
1522 IpAddr::V6(addr) => {
1523 buf.push(16);
1524 buf.extend_from_slice(&addr.octets());
1525 }
1526 }
1527 buf.push(0); crate::evpn::encode_evpn_nlri(&mp.evpn_announced, buf);
1529 return;
1530 }
1531
1532 match (mp.next_hop, mp.link_local_next_hop) {
1533 (IpAddr::V4(addr), _) => {
1534 buf.push(4); buf.extend_from_slice(&addr.octets());
1536 }
1537 (IpAddr::V6(addr), Some(ll)) => {
1538 debug_assert!(
1550 (ll.segments()[0] & 0xffc0) == 0xfe80,
1551 "MP_REACH NH-Len=32 second segment must be link-local (fe80::/10), got {ll}"
1552 );
1553 buf.push(32); buf.extend_from_slice(&addr.octets());
1555 buf.extend_from_slice(&ll.octets());
1556 }
1557 (IpAddr::V6(addr), None) => {
1558 buf.push(16); buf.extend_from_slice(&addr.octets());
1560 }
1561 }
1562
1563 buf.push(0); if add_path {
1566 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1567 } else {
1568 for entry in &mp.announced {
1569 match entry.prefix {
1570 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1571 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1572 }
1573 }
1574 }
1575}
1576
1577fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1581 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1582 buf.push(mp.safi as u8);
1583
1584 if mp.safi == Safi::FlowSpec {
1586 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1587 return;
1588 }
1589
1590 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1592 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1593 return;
1594 }
1595
1596 if add_path {
1597 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1598 } else {
1599 for entry in &mp.withdrawn {
1600 match entry.prefix {
1601 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1602 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1603 }
1604 }
1605 }
1606}
1607
1608fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1610 for segment in &as_path.segments {
1611 let (seg_type, asns) = match segment {
1612 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1613 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1614 };
1615 for chunk in asns.chunks(u8::MAX as usize) {
1616 buf.push(seg_type);
1617 #[expect(clippy::cast_possible_truncation)]
1618 buf.push(chunk.len() as u8);
1619 for &asn in chunk {
1620 if four_octet_as {
1621 buf.extend_from_slice(&asn.to_be_bytes());
1622 } else {
1623 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1626 buf.extend_from_slice(&as2.to_be_bytes());
1627 }
1628 }
1629 }
1630 }
1631}
1632
1633#[cfg(test)]
1634mod tests {
1635 use super::*;
1636
1637 #[test]
1638 fn mp_reach_evpn_attribute_roundtrip() {
1639 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1640
1641 let mp = MpReachNlri {
1642 afi: Afi::L2Vpn,
1643 safi: Safi::Evpn,
1644 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1645 link_local_next_hop: None,
1646 announced: vec![],
1647 flowspec_announced: vec![],
1648 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1649 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1650 ethernet_tag: EthernetTagId(100),
1651 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1652 })],
1653 };
1654 let attr = PathAttribute::MpReachNlri(mp);
1655
1656 let mut buf = Vec::new();
1657 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1658 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1659 assert_eq!(decoded.len(), 1);
1660 assert_eq!(attr, decoded[0]);
1661
1662 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1663 panic!("not MP_REACH after decode");
1664 };
1665 assert_eq!(dec.afi, Afi::L2Vpn);
1666 assert_eq!(dec.safi, Safi::Evpn);
1667 assert_eq!(dec.evpn_announced.len(), 1);
1668 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1669 }
1670
1671 #[test]
1682 fn mp_reach_evpn_ipv6_next_hop_roundtrip() {
1683 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1684
1685 let vtep_v6: Ipv6Addr = "2001:db8:dead::1".parse().unwrap();
1686 let mp = MpReachNlri {
1687 afi: Afi::L2Vpn,
1688 safi: Safi::Evpn,
1689 next_hop: IpAddr::V6(vtep_v6),
1690 link_local_next_hop: None,
1691 announced: vec![],
1692 flowspec_announced: vec![],
1693 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1694 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1695 ethernet_tag: EthernetTagId(100),
1696 originator_ip: IpAddr::V6(vtep_v6),
1697 })],
1698 };
1699 let attr = PathAttribute::MpReachNlri(mp.clone());
1700
1701 let mut buf = Vec::new();
1702 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1703
1704 let extended = (buf[0] & 0x10) != 0;
1715 let value_off = if extended { 4 } else { 3 };
1716 assert_eq!(
1717 buf[value_off + 3],
1718 16,
1719 "EVPN IPv6 NH-Len must be 16, not 32"
1720 );
1721 assert_eq!(
1722 &buf[value_off + 4..value_off + 20],
1723 &vtep_v6.octets(),
1724 "encoded VTEP next-hop bytes must match the input"
1725 );
1726
1727 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1728 assert_eq!(decoded.len(), 1);
1729 assert_eq!(PathAttribute::MpReachNlri(mp), decoded[0]);
1730
1731 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1732 panic!("not MP_REACH after decode");
1733 };
1734 assert_eq!(dec.afi, Afi::L2Vpn);
1735 assert_eq!(dec.safi, Safi::Evpn);
1736 assert_eq!(dec.next_hop, IpAddr::V6(vtep_v6));
1737 assert!(
1738 dec.link_local_next_hop.is_none(),
1739 "EVPN's 16-byte form must not synthesize a link-local next-hop"
1740 );
1741 assert_eq!(dec.evpn_announced.len(), 1);
1742 match &dec.evpn_announced[0] {
1743 EvpnRoute::Imet(imet) => {
1744 assert_eq!(imet.originator_ip, IpAddr::V6(vtep_v6));
1745 assert_eq!(imet.ethernet_tag, EthernetTagId(100));
1746 }
1747 other => panic!("expected IMET, got {other:?}"),
1748 }
1749 }
1750
1751 #[test]
1758 fn mp_reach_evpn_rejects_32byte_next_hop() {
1759 let mut attr = vec![0x80u8, 14, 37];
1765 attr.extend_from_slice(&[
1766 0x00, 0x19, 0x46, 0x20, ]);
1770 attr.extend(std::iter::repeat_n(0u8, 32)); attr.push(0); let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
1774 match err {
1775 DecodeError::MalformedField { detail, .. } => {
1776 assert!(
1777 detail.contains("L2VPN next-hop length 32"),
1778 "expected L2VPN NH-Len rejection, got: {detail}"
1779 );
1780 }
1781 other => panic!("expected MalformedField, got: {other:?}"),
1782 }
1783 }
1784
1785 #[test]
1786 fn mp_unreach_evpn_attribute_roundtrip() {
1787 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1788
1789 let mp = MpUnreachNlri {
1790 afi: Afi::L2Vpn,
1791 safi: Safi::Evpn,
1792 withdrawn: vec![],
1793 flowspec_withdrawn: vec![],
1794 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1795 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1796 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1797 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1798 })],
1799 };
1800 let attr = PathAttribute::MpUnreachNlri(mp);
1801 let mut buf = Vec::new();
1802 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1803 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1804 assert_eq!(decoded.len(), 1);
1805 assert_eq!(attr, decoded[0]);
1806 }
1807
1808 #[test]
1811 fn ext_comm_bgp_encapsulation_vxlan() {
1812 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1814 assert_eq!(c.subtype(), 0x0C);
1815 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1816 let b = c.as_u64().to_be_bytes();
1818 assert_eq!(b[2..6], [0, 0, 0, 0]);
1819 assert_eq!(&b[6..8], &[0, 8]);
1820 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1822 }
1823
1824 #[test]
1825 fn ext_comm_mac_mobility_sticky_and_sequence() {
1826 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1827 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1828 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1829 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1830 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1832 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1833 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1834 }
1835
1836 #[test]
1837 fn ext_comm_esi_label_flags_and_label() {
1838 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1839 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1840 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1841 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1842 }
1843
1844 #[test]
1845 fn ext_comm_es_import_rt_mac() {
1846 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1847 let e = ExtendedCommunity::es_import_rt(mac);
1848 assert_eq!(e.as_es_import_rt(), Some(mac));
1849 assert_eq!(e.type_byte(), 0x06);
1850 assert_eq!(e.subtype(), 0x02);
1851 }
1852
1853 #[test]
1854 fn ext_comm_df_election_hrw_roundtrips_reserved_bytes_zero() {
1855 let ec = ExtendedCommunity::df_election(1, 0, None);
1856 assert_eq!(ec.type_byte(), 0x06);
1857 assert_eq!(ec.subtype(), 0x06);
1858 assert_eq!(
1859 ec.as_df_election(),
1860 Some(DfElectionExtendedCommunity {
1861 algorithm_id: 1,
1862 capabilities: 0,
1863 preference: None,
1864 })
1865 );
1866 assert_eq!(ec.as_u64().to_be_bytes(), [0x06, 0x06, 0x01, 0, 0, 0, 0, 0]);
1867 }
1868
1869 #[test]
1870 fn ext_comm_df_election_preference_bytes_decode_for_rfc9785_algorithms() {
1871 let ec = ExtendedCommunity::df_election(3, 0x8000, Some(42));
1872 assert_eq!(
1873 ec.as_df_election(),
1874 Some(DfElectionExtendedCommunity {
1875 algorithm_id: 3,
1876 capabilities: 0x8000,
1877 preference: Some(42),
1878 })
1879 );
1880 }
1881
1882 #[test]
1883 fn ext_comm_router_mac() {
1884 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1885 let e = ExtendedCommunity::router_mac(mac);
1886 assert_eq!(e.as_router_mac(), Some(mac));
1887 }
1888
1889 #[test]
1890 fn ext_comm_default_gateway_flag_only() {
1891 let d = ExtendedCommunity::default_gateway();
1892 assert!(d.as_default_gateway());
1893 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1895 }
1896
1897 #[test]
1901 fn ext_comm_default_gateway_rejects_nonzero_value() {
1902 let malformed =
1904 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1905 assert!(
1906 !malformed.as_default_gateway(),
1907 "default-gateway accessor must require all-zero value bytes"
1908 );
1909 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1911 }
1912
1913 #[test]
1914 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1915 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1917 assert_eq!(rt.as_mac_mobility(), None);
1918 assert_eq!(rt.as_esi_label(), None);
1919 assert_eq!(rt.as_es_import_rt(), None);
1920 assert_eq!(rt.as_router_mac(), None);
1921 assert!(!rt.as_default_gateway());
1922 }
1923
1924 #[test]
1925 fn origin_from_u8_roundtrip() {
1926 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1927 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1928 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1929 assert_eq!(Origin::from_u8(3), None);
1930 }
1931
1932 #[test]
1933 fn origin_ordering() {
1934 assert!(Origin::Igp < Origin::Egp);
1935 assert!(Origin::Egp < Origin::Incomplete);
1936 }
1937
1938 #[test]
1939 fn as_path_length_calculation() {
1940 let path = AsPath {
1941 segments: vec![
1942 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1943 AsPathSegment::AsSet(vec![65004, 65005]),
1944 ],
1945 };
1946 assert_eq!(path.len(), 4);
1948 }
1949
1950 #[test]
1951 fn as_path_empty() {
1952 let path = AsPath { segments: vec![] };
1953 assert!(path.is_empty());
1954 assert_eq!(path.len(), 0);
1955 }
1956
1957 #[test]
1958 fn contains_asn_in_sequence() {
1959 let path = AsPath {
1960 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1961 };
1962 assert!(path.contains_asn(65002));
1963 assert!(!path.contains_asn(65004));
1964 }
1965
1966 #[test]
1967 fn contains_asn_in_set() {
1968 let path = AsPath {
1969 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1970 };
1971 assert!(path.contains_asn(65005));
1972 assert!(!path.contains_asn(65001));
1973 }
1974
1975 #[test]
1976 fn contains_asn_multiple_segments() {
1977 let path = AsPath {
1978 segments: vec![
1979 AsPathSegment::AsSequence(vec![65001, 65002]),
1980 AsPathSegment::AsSet(vec![65003]),
1981 ],
1982 };
1983 assert!(path.contains_asn(65001));
1984 assert!(path.contains_asn(65003));
1985 assert!(!path.contains_asn(65004));
1986 }
1987
1988 #[test]
1989 fn contains_asn_empty_path() {
1990 let path = AsPath { segments: vec![] };
1991 assert!(!path.contains_asn(65001));
1992 }
1993
1994 #[test]
1995 fn is_private_asn_boundaries() {
1996 assert!(!is_private_asn(64_511));
1998 assert!(is_private_asn(64_512));
1999 assert!(is_private_asn(65_534));
2000 assert!(!is_private_asn(65_535));
2001
2002 assert!(!is_private_asn(4_199_999_999));
2004 assert!(is_private_asn(4_200_000_000));
2005 assert!(is_private_asn(4_294_967_294));
2006 assert!(!is_private_asn(4_294_967_295));
2007 }
2008
2009 #[test]
2010 fn all_private_empty_path_is_false() {
2011 let path = AsPath { segments: vec![] };
2012 assert!(!path.all_private());
2013 }
2014
2015 #[test]
2016 fn all_private_mixed_segments() {
2017 let path = AsPath {
2018 segments: vec![
2019 AsPathSegment::AsSet(vec![64_512, 65_000]),
2020 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
2021 ],
2022 };
2023 assert!(path.all_private());
2024
2025 let non_private = AsPath {
2026 segments: vec![
2027 AsPathSegment::AsSet(vec![64_512, 65_000]),
2028 AsPathSegment::AsSequence(vec![65_535]),
2029 ],
2030 };
2031 assert!(!non_private.all_private());
2032 }
2033
2034 #[test]
2035 fn decode_origin_igp() {
2036 let buf = [0x40, 0x01, 0x01, 0x00];
2038 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2039 assert_eq!(attrs.len(), 1);
2040 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2041 }
2042
2043 #[test]
2044 fn decode_origin_egp() {
2045 let buf = [0x40, 0x01, 0x01, 0x01];
2046 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2047 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
2048 }
2049
2050 #[test]
2051 fn decode_origin_invalid_value() {
2052 let buf = [0x40, 0x01, 0x01, 0x05];
2054 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2055 match &err {
2056 DecodeError::UpdateAttributeError { subcode, .. } => {
2057 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2058 }
2059 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2060 }
2061 }
2062
2063 #[test]
2064 fn decode_next_hop() {
2065 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
2067 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2068 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2069 }
2070
2071 #[test]
2072 fn decode_med() {
2073 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
2075 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2076 assert_eq!(attrs[0], PathAttribute::Med(100));
2077 }
2078
2079 #[test]
2080 fn decode_local_pref() {
2081 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
2083 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2084 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
2085 }
2086
2087 #[test]
2088 fn decode_as_path_4byte() {
2089 let buf = [
2092 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2097 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2098 assert_eq!(
2099 attrs[0],
2100 PathAttribute::AsPath(AsPath {
2101 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2102 })
2103 );
2104 }
2105
2106 #[test]
2107 fn decode_as_path_2byte() {
2108 let buf = [
2111 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
2116 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
2117 assert_eq!(
2118 attrs[0],
2119 PathAttribute::AsPath(AsPath {
2120 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
2121 })
2122 );
2123 }
2124
2125 #[test]
2126 fn decode_unknown_attribute_preserved() {
2127 let buf = [0xC0, 99, 0x03, 1, 2, 3];
2129 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2130 assert_eq!(
2131 attrs[0],
2132 PathAttribute::Unknown(RawAttribute {
2133 flags: 0xC0,
2134 type_code: 99,
2135 data: Bytes::from_static(&[1, 2, 3]),
2136 })
2137 );
2138 }
2139
2140 #[test]
2141 fn decode_atomic_aggregate_as_unknown() {
2142 let buf = [0x40, 0x06, 0x00];
2144 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2145 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
2146 }
2147
2148 #[test]
2149 fn decode_extended_length() {
2150 let buf = [
2153 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
2158 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2159 assert_eq!(
2160 attrs[0],
2161 PathAttribute::AsPath(AsPath {
2162 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
2163 })
2164 );
2165 }
2166
2167 #[test]
2168 fn decode_multiple_attributes() {
2169 let mut buf = Vec::new();
2170 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
2172 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
2174 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
2176
2177 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2178 assert_eq!(attrs.len(), 3);
2179 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
2180 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
2181 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
2182 }
2183
2184 #[test]
2185 fn roundtrip_attributes_4byte() {
2186 let attrs = vec![
2187 PathAttribute::Origin(Origin::Igp),
2188 PathAttribute::AsPath(AsPath {
2189 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
2190 }),
2191 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
2192 PathAttribute::Med(100),
2193 PathAttribute::LocalPref(200),
2194 ];
2195
2196 let mut buf = Vec::new();
2197 encode_path_attributes(&attrs, &mut buf, true, false);
2198 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2199 assert_eq!(decoded, attrs);
2200 }
2201
2202 #[test]
2203 fn roundtrip_attributes_2byte() {
2204 let attrs = vec![
2205 PathAttribute::Origin(Origin::Egp),
2206 PathAttribute::AsPath(AsPath {
2207 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
2208 }),
2209 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
2210 ];
2211
2212 let mut buf = Vec::new();
2213 encode_path_attributes(&attrs, &mut buf, false, false);
2214 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
2215 assert_eq!(decoded, attrs);
2216 }
2217
2218 #[test]
2219 fn reject_truncated_attribute_header() {
2220 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
2222 }
2223
2224 #[test]
2225 fn reject_truncated_attribute_value() {
2226 let buf = [0x40, 0x01, 0x01];
2228 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2229 }
2230
2231 #[test]
2232 fn reject_bad_origin_length() {
2233 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2235 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2236 }
2237
2238 #[test]
2239 fn as_path_with_set_and_sequence() {
2240 let attrs = vec![PathAttribute::AsPath(AsPath {
2242 segments: vec![
2243 AsPathSegment::AsSequence(vec![65001]),
2244 AsPathSegment::AsSet(vec![65002, 65003]),
2245 ],
2246 })];
2247
2248 let mut buf = Vec::new();
2249 encode_path_attributes(&attrs, &mut buf, true, false);
2250 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2251 assert_eq!(decoded, attrs);
2252 }
2253
2254 #[test]
2255 fn decode_communities_single() {
2256 let community: u32 = (65001 << 16) | 0x0064;
2259 let bytes = community.to_be_bytes();
2260 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2261 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2262 assert_eq!(attrs.len(), 1);
2263 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2264 }
2265
2266 #[test]
2267 fn decode_communities_multiple() {
2268 let c1: u32 = (65001 << 16) | 0x0064;
2269 let c2: u32 = (65002 << 16) | 0x00C8;
2270 let b1 = c1.to_be_bytes();
2271 let b2 = c2.to_be_bytes();
2272 let buf = [
2273 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2274 ];
2275 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2276 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2277 }
2278
2279 #[test]
2280 fn decode_communities_empty() {
2281 let buf = [0xC0, 0x08, 0x00];
2283 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2284 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2285 }
2286
2287 #[test]
2288 fn decode_communities_odd_length_rejected() {
2289 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2291 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2292 }
2293
2294 #[test]
2295 fn communities_roundtrip() {
2296 let c1: u32 = (65001 << 16) | 0x0064;
2297 let c2: u32 = (65002 << 16) | 0x00C8;
2298 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2299
2300 let mut buf = Vec::new();
2301 encode_path_attributes(&attrs, &mut buf, true, false);
2302 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2303 assert_eq!(decoded, attrs);
2304 }
2305
2306 #[test]
2307 fn communities_type_code_and_flags() {
2308 let attr = PathAttribute::Communities(vec![]);
2309 assert_eq!(attr.type_code(), 8);
2310 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2311 }
2312
2313 #[test]
2316 fn decode_extended_communities_single() {
2317 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2319 let bytes = ec.as_u64().to_be_bytes();
2320 let buf = [
2321 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2322 bytes[7],
2323 ];
2324 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2325 assert_eq!(attrs.len(), 1);
2326 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2327 }
2328
2329 #[test]
2330 fn decode_extended_communities_multiple() {
2331 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2332 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2333 let b1 = ec1.as_u64().to_be_bytes();
2334 let b2 = ec2.as_u64().to_be_bytes();
2335 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2337 buf.extend_from_slice(&b2);
2338 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2339 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2340 }
2341
2342 #[test]
2343 fn decode_extended_communities_empty() {
2344 let buf = [0xC0, 0x10, 0x00];
2345 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2346 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2347 }
2348
2349 #[test]
2350 fn decode_extended_communities_bad_length() {
2351 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2353 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2354 }
2355
2356 #[test]
2357 fn extended_communities_roundtrip() {
2358 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2359 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2360 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2361
2362 let mut buf = Vec::new();
2363 encode_path_attributes(&attrs, &mut buf, true, false);
2364 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2365 assert_eq!(decoded, attrs);
2366 }
2367
2368 #[test]
2369 fn extended_communities_type_code_and_flags() {
2370 let attr = PathAttribute::ExtendedCommunities(vec![]);
2371 assert_eq!(attr.type_code(), 16);
2372 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2373 }
2374
2375 #[test]
2376 fn extended_community_type_subtype() {
2377 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2379 assert_eq!(ec.type_byte(), 0x00);
2380 assert_eq!(ec.subtype(), 0x02);
2381 assert!(ec.is_transitive());
2382 }
2383
2384 #[test]
2385 fn extended_community_route_target() {
2386 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2388 assert_eq!(ec.route_target(), Some((65001, 100)));
2389 assert_eq!(ec.route_origin(), None);
2390
2391 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2393 assert_eq!(ec4.route_target(), Some((65537, 200)));
2394
2395 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2398 let (g, l) = ec_ipv4.route_target().unwrap();
2399 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2401 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2403 }
2404
2405 #[test]
2406 fn extended_community_is_transitive() {
2407 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2409 assert!(t.is_transitive());
2410
2411 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2413 assert!(!nt.is_transitive());
2414 }
2415
2416 #[test]
2417 fn extended_community_display() {
2418 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2419 assert_eq!(rt.to_string(), "RT:65001:100");
2420
2421 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2422 assert_eq!(ro.to_string(), "RO:65001:100");
2423
2424 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2426 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2427
2428 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2430 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2431
2432 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2434 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2435
2436 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2438 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2439 }
2440
2441 #[test]
2442 fn unknown_attribute_roundtrip() {
2443 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2446 flags: 0xC0,
2447 type_code: 99,
2448 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2449 })];
2450
2451 let mut buf = Vec::new();
2452 encode_path_attributes(&attrs, &mut buf, true, false);
2453 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2454 assert_eq!(
2455 decoded,
2456 vec![PathAttribute::Unknown(RawAttribute {
2457 flags: 0xE0, type_code: 99,
2459 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2460 })]
2461 );
2462 }
2463
2464 #[test]
2465 fn origin_with_optional_flag_rejected() {
2466 let buf = [0xC0, 0x01, 0x01, 0x00];
2468 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2469 match &err {
2470 DecodeError::UpdateAttributeError { subcode, .. } => {
2471 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2472 }
2473 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2474 }
2475 }
2476
2477 #[test]
2478 fn med_with_transitive_flag_rejected() {
2479 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2481 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2482 match &err {
2483 DecodeError::UpdateAttributeError { subcode, .. } => {
2484 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2485 }
2486 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2487 }
2488 }
2489
2490 #[test]
2491 fn communities_without_optional_rejected() {
2492 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2494 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2495 match &err {
2496 DecodeError::UpdateAttributeError { subcode, .. } => {
2497 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2498 }
2499 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2500 }
2501 }
2502
2503 #[test]
2504 fn next_hop_length_error_subcode() {
2505 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2507 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2508 match &err {
2509 DecodeError::UpdateAttributeError { subcode, .. } => {
2510 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2511 }
2512 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2513 }
2514 }
2515
2516 #[test]
2517 fn invalid_origin_value_subcode() {
2518 let buf = [0x40, 0x01, 0x01, 0x05];
2520 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2521 match &err {
2522 DecodeError::UpdateAttributeError { subcode, .. } => {
2523 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2524 }
2525 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2526 }
2527 }
2528
2529 #[test]
2530 fn as_path_bad_segment_subcode() {
2531 let buf = [
2533 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2537 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2538 match &err {
2539 DecodeError::UpdateAttributeError { subcode, .. } => {
2540 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2541 }
2542 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2543 }
2544 }
2545
2546 #[test]
2547 fn encode_unknown_transitive_sets_partial() {
2548 let attr = PathAttribute::Unknown(RawAttribute {
2549 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2551 data: Bytes::from_static(&[1, 2]),
2552 });
2553 let mut buf = Vec::new();
2554 encode_path_attributes(&[attr], &mut buf, true, false);
2555 assert_eq!(
2557 buf[0],
2558 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2559 );
2560 }
2561
2562 #[test]
2563 fn encode_unknown_wellknown_transitive_no_partial() {
2564 let attr = PathAttribute::Unknown(RawAttribute {
2566 flags: attr_flags::TRANSITIVE, type_code: 99,
2568 data: Bytes::from_static(&[1, 2]),
2569 });
2570 let mut buf = Vec::new();
2571 encode_path_attributes(&[attr], &mut buf, true, false);
2572 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2573 }
2574
2575 #[test]
2576 fn encode_unknown_nontransitive_no_partial() {
2577 let attr = PathAttribute::Unknown(RawAttribute {
2578 flags: attr_flags::OPTIONAL, type_code: 99,
2580 data: Bytes::from_static(&[1, 2]),
2581 });
2582 let mut buf = Vec::new();
2583 encode_path_attributes(&[attr], &mut buf, true, false);
2584 assert_eq!(buf[0], attr_flags::OPTIONAL);
2586 }
2587
2588 fn nlri(prefix: Prefix) -> NlriEntry {
2592 NlriEntry { path_id: 0, prefix }
2593 }
2594
2595 #[test]
2596 fn mp_reach_nlri_ipv6_roundtrip() {
2597 use crate::capability::{Afi, Safi};
2598 use crate::nlri::{Ipv6Prefix, Prefix};
2599
2600 let mp = MpReachNlri {
2601 afi: Afi::Ipv6,
2602 safi: Safi::Unicast,
2603 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2604 link_local_next_hop: None,
2605 announced: vec![
2606 nlri(Prefix::V6(Ipv6Prefix::new(
2607 "2001:db8:1::".parse().unwrap(),
2608 48,
2609 ))),
2610 nlri(Prefix::V6(Ipv6Prefix::new(
2611 "2001:db8:2::".parse().unwrap(),
2612 48,
2613 ))),
2614 ],
2615 flowspec_announced: vec![],
2616 evpn_announced: vec![],
2617 };
2618 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2619
2620 let mut buf = Vec::new();
2621 encode_path_attributes(&attrs, &mut buf, true, false);
2622 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2623 assert_eq!(decoded.len(), 1);
2624 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2625 }
2626
2627 #[test]
2628 fn mp_unreach_nlri_ipv6_roundtrip() {
2629 use crate::capability::{Afi, Safi};
2630 use crate::nlri::{Ipv6Prefix, Prefix};
2631
2632 let mp = MpUnreachNlri {
2633 afi: Afi::Ipv6,
2634 safi: Safi::Unicast,
2635 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2636 "2001:db8:1::".parse().unwrap(),
2637 48,
2638 )))],
2639 flowspec_withdrawn: vec![],
2640 evpn_withdrawn: vec![],
2641 };
2642 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2643
2644 let mut buf = Vec::new();
2645 encode_path_attributes(&attrs, &mut buf, true, false);
2646 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2647 assert_eq!(decoded.len(), 1);
2648 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2649 }
2650
2651 #[test]
2652 fn mp_reach_nlri_ipv4_roundtrip() {
2653 use crate::capability::{Afi, Safi};
2654 use crate::nlri::Prefix;
2655
2656 let mp = MpReachNlri {
2657 afi: Afi::Ipv4,
2658 safi: Safi::Unicast,
2659 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2660 link_local_next_hop: None,
2661 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2662 Ipv4Addr::new(10, 1, 0, 0),
2663 16,
2664 )))],
2665 flowspec_announced: vec![],
2666 evpn_announced: vec![],
2667 };
2668 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2669
2670 let mut buf = Vec::new();
2671 encode_path_attributes(&attrs, &mut buf, true, false);
2672 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2673 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2674 }
2675
2676 #[test]
2677 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2678 use crate::capability::{Afi, Safi};
2679 use crate::nlri::Prefix;
2680
2681 let mp = MpReachNlri {
2682 afi: Afi::Ipv4,
2683 safi: Safi::Unicast,
2684 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2685 link_local_next_hop: None,
2686 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2687 Ipv4Addr::new(10, 1, 0, 0),
2688 16,
2689 )))],
2690 flowspec_announced: vec![],
2691 evpn_announced: vec![],
2692 };
2693 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2694
2695 let mut buf = Vec::new();
2696 encode_path_attributes(&attrs, &mut buf, true, false);
2697 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2698 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2699 }
2700
2701 #[test]
2702 fn mp_reach_nlri_type_code_and_flags() {
2703 use crate::capability::{Afi, Safi};
2704
2705 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2706 afi: Afi::Ipv6,
2707 safi: Safi::Unicast,
2708 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2709 link_local_next_hop: None,
2710 announced: vec![],
2711 flowspec_announced: vec![],
2712 evpn_announced: vec![],
2713 });
2714 assert_eq!(attr.type_code(), 14);
2715 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2717 }
2718
2719 #[test]
2720 fn mp_unreach_nlri_type_code_and_flags() {
2721 use crate::capability::{Afi, Safi};
2722
2723 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2724 afi: Afi::Ipv6,
2725 safi: Safi::Unicast,
2726 withdrawn: vec![],
2727 flowspec_withdrawn: vec![],
2728 evpn_withdrawn: vec![],
2729 });
2730 assert_eq!(attr.type_code(), 15);
2731 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2732 }
2733
2734 #[test]
2735 fn mp_reach_nlri_empty_nlri() {
2736 use crate::capability::{Afi, Safi};
2737
2738 let mp = MpReachNlri {
2739 afi: Afi::Ipv6,
2740 safi: Safi::Unicast,
2741 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2742 link_local_next_hop: None,
2743 announced: vec![],
2744 flowspec_announced: vec![],
2745 evpn_announced: vec![],
2746 };
2747 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2748
2749 let mut buf = Vec::new();
2750 encode_path_attributes(&attrs, &mut buf, true, false);
2751 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2752 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2753 }
2754
2755 #[test]
2756 fn mp_reach_nlri_bad_flags_rejected() {
2757 let mut value = Vec::new();
2761 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();
2768 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2771 buf.push(value.len() as u8);
2772 buf.extend_from_slice(&value);
2773
2774 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2775 assert!(matches!(
2776 err,
2777 DecodeError::UpdateAttributeError {
2778 subcode: 4, ..
2780 }
2781 ));
2782 }
2783
2784 #[test]
2787 #[expect(clippy::cast_possible_truncation)]
2788 fn mp_reach_nlri_ipv4_addpath_decode() {
2789 use crate::capability::{Afi, Safi};
2790 use crate::nlri::Prefix;
2791
2792 let mut value = Vec::new();
2795 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());
2802 value.push(16);
2803 value.extend_from_slice(&[10, 1]);
2804
2805 let mut buf = Vec::new();
2806 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2809 buf.extend_from_slice(&value);
2810
2811 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2813 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2814 panic!("expected MpReachNlri");
2815 };
2816 assert_eq!(mp.announced.len(), 1);
2817 assert_eq!(mp.announced[0].path_id, 42);
2818 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2819
2820 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2823 }
2824
2825 #[test]
2826 #[expect(clippy::cast_possible_truncation)]
2827 fn mp_reach_nlri_ipv6_addpath_decode() {
2828 use crate::capability::{Afi, Safi};
2829 use crate::nlri::{Ipv6Prefix, Prefix};
2830
2831 let mut value = Vec::new();
2833 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());
2837 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2840 value.push(48);
2841 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2842
2843 let mut buf = Vec::new();
2844 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2847 buf.extend_from_slice(&value);
2848
2849 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2850 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2851 panic!("expected MpReachNlri");
2852 };
2853 assert_eq!(mp.announced.len(), 1);
2854 assert_eq!(mp.announced[0].path_id, 99);
2855 assert_eq!(
2856 mp.announced[0].prefix,
2857 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2858 );
2859 }
2860
2861 #[test]
2862 #[expect(clippy::cast_possible_truncation)]
2863 fn mp_unreach_nlri_ipv6_addpath_decode() {
2864 use crate::capability::{Afi, Safi};
2865 use crate::nlri::{Ipv6Prefix, Prefix};
2866
2867 let mut value = Vec::new();
2869 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2873 value.push(48);
2874 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2875
2876 let mut buf = Vec::new();
2877 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2880 buf.extend_from_slice(&value);
2881
2882 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2883 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2884 panic!("expected MpUnreachNlri");
2885 };
2886 assert_eq!(mp.withdrawn.len(), 1);
2887 assert_eq!(mp.withdrawn[0].path_id, 7);
2888 assert_eq!(
2889 mp.withdrawn[0].prefix,
2890 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2891 );
2892 }
2893
2894 #[test]
2895 fn mp_reach_addpath_only_applies_to_matching_family() {
2896 use crate::capability::{Afi, Safi};
2897 use crate::nlri::{Ipv6Prefix, Prefix};
2898
2899 let mp = MpReachNlri {
2901 afi: Afi::Ipv6,
2902 safi: Safi::Unicast,
2903 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2904 link_local_next_hop: None,
2905 announced: vec![NlriEntry {
2906 path_id: 0,
2907 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2908 }],
2909 flowspec_announced: vec![],
2910 evpn_announced: vec![],
2911 };
2912 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2913
2914 let mut buf = Vec::new();
2915 encode_path_attributes(&attrs, &mut buf, true, false);
2916
2917 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2919 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2920 }
2921
2922 #[test]
2925 fn decode_originator_id() {
2926 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2928 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2929 assert_eq!(
2930 attrs[0],
2931 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2932 );
2933 }
2934
2935 #[test]
2940 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
2941 use crate::capability::{Afi, Safi};
2942 use crate::nlri::{Ipv6Prefix, Prefix};
2943 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
2944 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
2945 let mp = MpReachNlri {
2946 afi: Afi::Ipv6,
2947 safi: Safi::Unicast,
2948 next_hop: IpAddr::V6(global),
2949 link_local_next_hop: Some(link_local),
2950 announced: vec![NlriEntry {
2951 path_id: 0,
2952 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2953 }],
2954 flowspec_announced: vec![],
2955 evpn_announced: vec![],
2956 };
2957 let attr = PathAttribute::MpReachNlri(mp.clone());
2958 let mut buf = Vec::new();
2959 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2960
2961 let extended = (buf[0] & 0x10) != 0;
2965 let value_off = if extended { 4 } else { 3 };
2966 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
2968 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
2969 assert_eq!(
2970 &buf[value_off + 20..value_off + 36],
2971 &link_local.octets(),
2972 "encoded link-local bytes must match the input"
2973 );
2974
2975 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2976 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
2977 panic!("expected MpReachNlri");
2978 };
2979 assert_eq!(dec.next_hop, IpAddr::V6(global));
2980 assert_eq!(dec.link_local_next_hop, Some(link_local));
2981 }
2982
2983 #[test]
2991 fn mp_reach_flowspec_rejects_nonzero_nh_len() {
2992 let value: &[u8] = &[
2995 0x00, 0x01, 0x85, 0x04, 10, 0, 0, 1, 0x00, 0x07, 0x01, 0x18, 192, 168, 1,
3002 ];
3003 let mut attr = vec![0x80, 14, u8::try_from(value.len()).unwrap()];
3006 attr.extend_from_slice(value);
3007 let err = decode_path_attributes(&attr, true, &[]).unwrap_err();
3008 match err {
3009 DecodeError::MalformedField { detail, .. } => {
3010 assert!(
3011 detail.contains("FlowSpec next-hop length"),
3012 "expected FlowSpec NH-Len rejection, got: {detail}"
3013 );
3014 }
3015 other => panic!("expected MalformedField, got {other:?}"),
3016 }
3017 }
3018
3019 #[test]
3020 fn originator_id_roundtrip() {
3021 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
3022 let mut buf = Vec::new();
3023 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3024 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3025 assert_eq!(decoded, vec![attr]);
3026 }
3027
3028 #[test]
3029 fn originator_id_wrong_length() {
3030 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
3032 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3033 assert!(matches!(
3034 err,
3035 DecodeError::UpdateAttributeError {
3036 subcode: 5, ..
3038 }
3039 ));
3040 }
3041
3042 #[test]
3043 fn originator_id_wrong_flags() {
3044 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
3046 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3047 assert!(matches!(
3048 err,
3049 DecodeError::UpdateAttributeError {
3050 subcode: 4, ..
3052 }
3053 ));
3054 }
3055
3056 #[test]
3059 fn decode_cluster_list() {
3060 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
3062 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3063 assert_eq!(
3064 attrs[0],
3065 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
3066 );
3067 }
3068
3069 #[test]
3070 fn cluster_list_roundtrip() {
3071 let attr = PathAttribute::ClusterList(vec![
3072 Ipv4Addr::new(10, 0, 0, 1),
3073 Ipv4Addr::new(10, 0, 0, 2),
3074 ]);
3075 let mut buf = Vec::new();
3076 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
3077 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3078 assert_eq!(decoded, vec![attr]);
3079 }
3080
3081 #[test]
3082 fn cluster_list_wrong_length() {
3083 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
3085 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3086 assert!(matches!(
3087 err,
3088 DecodeError::UpdateAttributeError {
3089 subcode: 5, ..
3091 }
3092 ));
3093 }
3094
3095 #[test]
3100 fn large_community_display() {
3101 let lc = LargeCommunity::new(65001, 100, 200);
3102 assert_eq!(lc.to_string(), "65001:100:200");
3103 }
3104
3105 #[test]
3106 fn large_community_type_code_and_flags() {
3107 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
3108 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
3109 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
3110 }
3111
3112 #[test]
3113 fn decode_large_community_single() {
3114 let mut buf = vec![0xC0, 32, 12];
3116 buf.extend_from_slice(&65001u32.to_be_bytes());
3117 buf.extend_from_slice(&100u32.to_be_bytes());
3118 buf.extend_from_slice(&200u32.to_be_bytes());
3119 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3120 assert_eq!(attrs.len(), 1);
3121 assert_eq!(
3122 attrs[0],
3123 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
3124 );
3125 }
3126
3127 #[test]
3128 fn decode_large_community_multiple() {
3129 let mut buf = vec![0xC0, 32, 24];
3131 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
3132 buf.extend_from_slice(&g.to_be_bytes());
3133 buf.extend_from_slice(&l1.to_be_bytes());
3134 buf.extend_from_slice(&l2.to_be_bytes());
3135 }
3136 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
3137 assert_eq!(
3138 attrs[0],
3139 PathAttribute::LargeCommunities(vec![
3140 LargeCommunity::new(65001, 100, 200),
3141 LargeCommunity::new(65002, 300, 400),
3142 ])
3143 );
3144 }
3145
3146 #[test]
3147 fn decode_large_community_bad_length() {
3148 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
3150 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3151 assert!(matches!(
3152 err,
3153 DecodeError::UpdateAttributeError {
3154 subcode: 5, ..
3156 }
3157 ));
3158 }
3159
3160 #[test]
3161 fn decode_large_community_empty_rejected() {
3162 let buf = [0xC0, 32, 0];
3164 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3165 assert!(matches!(
3166 err,
3167 DecodeError::UpdateAttributeError {
3168 subcode: 5, ..
3170 }
3171 ));
3172 }
3173
3174 #[test]
3175 fn large_community_roundtrip() {
3176 let lcs = vec![
3177 LargeCommunity::new(65001, 100, 200),
3178 LargeCommunity::new(0, u32::MAX, 42),
3179 ];
3180 let attr = PathAttribute::LargeCommunities(lcs.clone());
3181 let mut buf = Vec::new();
3182 encode_path_attributes(&[attr], &mut buf, true, false);
3183 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3184 assert_eq!(decoded.len(), 1);
3185 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
3186 }
3187
3188 #[test]
3189 fn large_community_expected_flags_validated() {
3190 let mut buf = vec![0x40, 32, 12];
3192 buf.extend_from_slice(&1u32.to_be_bytes());
3193 buf.extend_from_slice(&2u32.to_be_bytes());
3194 buf.extend_from_slice(&3u32.to_be_bytes());
3195 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3196 assert!(matches!(
3197 err,
3198 DecodeError::UpdateAttributeError {
3199 subcode: 4, ..
3201 }
3202 ));
3203 }
3204
3205 #[test]
3210 fn aspath_string_sequence() {
3211 let p = AsPath {
3212 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
3213 };
3214 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
3215 }
3216
3217 #[test]
3218 fn aspath_string_set() {
3219 let p = AsPath {
3220 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
3221 };
3222 assert_eq!(p.to_aspath_string(), "{65003 65004}");
3223 }
3224
3225 #[test]
3226 fn aspath_string_mixed() {
3227 let p = AsPath {
3228 segments: vec![
3229 AsPathSegment::AsSequence(vec![65001, 65002]),
3230 AsPathSegment::AsSet(vec![65003, 65004]),
3231 ],
3232 };
3233 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
3234 }
3235
3236 #[test]
3237 fn aspath_string_empty() {
3238 let p = AsPath { segments: vec![] };
3239 assert_eq!(p.to_aspath_string(), "");
3240 }
3241
3242 #[test]
3247 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3248 let bytes = vec![
3251 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
3257 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
3258 match err {
3259 DecodeError::MalformedField { detail, .. } => {
3260 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3261 }
3262 other => panic!("expected MalformedField, got {other:?}"),
3263 }
3264 }
3265
3266 #[test]
3267 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3268 let bytes = vec![
3269 0x00, 0x02, 70, 3, 0, ];
3273 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3274 match err {
3275 DecodeError::MalformedField { detail, .. } => {
3276 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3277 }
3278 other => panic!("expected MalformedField, got {other:?}"),
3279 }
3280 }
3281
3282 #[test]
3283 fn pmsi_tunnel_path_attribute_round_trips_through_dispatch() {
3284 let pmsi =
3289 crate::pmsi::PmsiTunnel::for_evpn_ingress_replication(100, "10.0.0.1".parse().unwrap());
3290 let attrs = vec![
3291 PathAttribute::Origin(Origin::Igp),
3292 PathAttribute::AsPath(AsPath { segments: vec![] }),
3293 PathAttribute::LocalPref(100),
3294 PathAttribute::PmsiTunnel(pmsi.clone()),
3295 ];
3296
3297 let mut buf = Vec::new();
3298 encode_path_attributes(&attrs, &mut buf, true, false);
3299 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
3300
3301 assert_eq!(decoded, attrs);
3302
3303 let pmsi_decoded = decoded
3306 .iter()
3307 .find_map(|a| match a {
3308 PathAttribute::PmsiTunnel(p) => Some(p),
3309 _ => None,
3310 })
3311 .expect("PMSI present");
3312 assert_eq!(pmsi_decoded, &pmsi);
3313 assert_eq!(
3314 PathAttribute::PmsiTunnel(pmsi).flags(),
3315 attr_flags::OPTIONAL | attr_flags::TRANSITIVE,
3316 );
3317 }
3318
3319 #[test]
3320 fn pmsi_tunnel_decode_attribute_with_truncated_value_is_malformed() {
3321 let buf = [
3323 0xC0, 22, 0x04, 0x00, 0x06, 0x00, 0x00,
3327 ];
3328 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
3329 assert!(matches!(err, DecodeError::MalformedField { .. }));
3330 }
3331}