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 buf.push(32); buf.extend_from_slice(&addr.octets());
1469 buf.extend_from_slice(&ll.octets());
1470 }
1471 (IpAddr::V6(addr), None) => {
1472 buf.push(16); buf.extend_from_slice(&addr.octets());
1474 }
1475 }
1476
1477 buf.push(0); if add_path {
1480 crate::nlri::encode_ipv6_nlri_addpath(&mp.announced, buf);
1481 } else {
1482 for entry in &mp.announced {
1483 match entry.prefix {
1484 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1485 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1486 }
1487 }
1488 }
1489}
1490
1491fn encode_mp_unreach_nlri(mp: &MpUnreachNlri, buf: &mut Vec<u8>, add_path: bool) {
1495 buf.extend_from_slice(&(mp.afi as u16).to_be_bytes());
1496 buf.push(mp.safi as u8);
1497
1498 if mp.safi == Safi::FlowSpec {
1500 crate::flowspec::encode_flowspec_nlri(&mp.flowspec_withdrawn, buf, mp.afi);
1501 return;
1502 }
1503
1504 if mp.afi == Afi::L2Vpn && mp.safi == Safi::Evpn {
1506 crate::evpn::encode_evpn_nlri(&mp.evpn_withdrawn, buf);
1507 return;
1508 }
1509
1510 if add_path {
1511 crate::nlri::encode_ipv6_nlri_addpath(&mp.withdrawn, buf);
1512 } else {
1513 for entry in &mp.withdrawn {
1514 match entry.prefix {
1515 Prefix::V4(p) => crate::nlri::encode_nlri(&[p], buf),
1516 Prefix::V6(p) => crate::nlri::encode_ipv6_nlri(&[p], buf),
1517 }
1518 }
1519 }
1520}
1521
1522fn encode_as_path(as_path: &AsPath, buf: &mut Vec<u8>, four_octet_as: bool) {
1524 for segment in &as_path.segments {
1525 let (seg_type, asns) = match segment {
1526 AsPathSegment::AsSet(asns) => (as_path_segment::AS_SET, asns),
1527 AsPathSegment::AsSequence(asns) => (as_path_segment::AS_SEQUENCE, asns),
1528 };
1529 for chunk in asns.chunks(u8::MAX as usize) {
1530 buf.push(seg_type);
1531 #[expect(clippy::cast_possible_truncation)]
1532 buf.push(chunk.len() as u8);
1533 for &asn in chunk {
1534 if four_octet_as {
1535 buf.extend_from_slice(&asn.to_be_bytes());
1536 } else {
1537 let as2 = u16::try_from(asn).unwrap_or(crate::constants::AS_TRANS);
1540 buf.extend_from_slice(&as2.to_be_bytes());
1541 }
1542 }
1543 }
1544 }
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549 use super::*;
1550
1551 #[test]
1552 fn mp_reach_evpn_attribute_roundtrip() {
1553 use crate::evpn::{EthernetTagId, EvpnImet, EvpnRoute, RouteDistinguisher};
1554
1555 let mp = MpReachNlri {
1556 afi: Afi::L2Vpn,
1557 safi: Safi::Evpn,
1558 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1559 link_local_next_hop: None,
1560 announced: vec![],
1561 flowspec_announced: vec![],
1562 evpn_announced: vec![EvpnRoute::Imet(EvpnImet {
1563 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1564 ethernet_tag: EthernetTagId(100),
1565 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 100)),
1566 })],
1567 };
1568 let attr = PathAttribute::MpReachNlri(mp);
1569
1570 let mut buf = Vec::new();
1571 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1572 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1573 assert_eq!(decoded.len(), 1);
1574 assert_eq!(attr, decoded[0]);
1575
1576 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
1577 panic!("not MP_REACH after decode");
1578 };
1579 assert_eq!(dec.afi, Afi::L2Vpn);
1580 assert_eq!(dec.safi, Safi::Evpn);
1581 assert_eq!(dec.evpn_announced.len(), 1);
1582 assert!(matches!(dec.evpn_announced[0], EvpnRoute::Imet(_)));
1583 }
1584
1585 #[test]
1586 fn mp_unreach_evpn_attribute_roundtrip() {
1587 use crate::evpn::{EthernetSegmentIdentifier, EvpnEs, EvpnRoute, RouteDistinguisher};
1588
1589 let mp = MpUnreachNlri {
1590 afi: Afi::L2Vpn,
1591 safi: Safi::Evpn,
1592 withdrawn: vec![],
1593 flowspec_withdrawn: vec![],
1594 evpn_withdrawn: vec![EvpnRoute::Es(EvpnEs {
1595 rd: RouteDistinguisher([0, 0, 0xFD, 0xE8, 0, 0, 0, 0x64]),
1596 esi: EthernetSegmentIdentifier([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
1597 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1598 })],
1599 };
1600 let attr = PathAttribute::MpUnreachNlri(mp);
1601 let mut buf = Vec::new();
1602 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
1603 let decoded = decode_path_attributes(&buf, true, &[]).expect("decode");
1604 assert_eq!(decoded.len(), 1);
1605 assert_eq!(attr, decoded[0]);
1606 }
1607
1608 #[test]
1611 fn ext_comm_bgp_encapsulation_vxlan() {
1612 let c = ExtendedCommunity::bgp_encapsulation(8); assert_eq!(c.type_byte(), 0x03);
1614 assert_eq!(c.subtype(), 0x0C);
1615 assert_eq!(c.as_bgp_encapsulation(), Some(8));
1616 let b = c.as_u64().to_be_bytes();
1618 assert_eq!(b[2..6], [0, 0, 0, 0]);
1619 assert_eq!(&b[6..8], &[0, 8]);
1620 assert_eq!(ExtendedCommunity::new(0).as_bgp_encapsulation(), None);
1622 }
1623
1624 #[test]
1625 fn ext_comm_mac_mobility_sticky_and_sequence() {
1626 let m1 = ExtendedCommunity::mac_mobility(false, 42);
1627 assert_eq!(m1.as_mac_mobility(), Some((false, 42)));
1628 let m2 = ExtendedCommunity::mac_mobility(true, 12345);
1629 assert_eq!(m2.as_mac_mobility(), Some((true, 12345)));
1630 let m3 = ExtendedCommunity::mac_mobility(true, u32::MAX);
1632 assert_eq!(m3.as_mac_mobility(), Some((true, u32::MAX)));
1633 assert_eq!(ExtendedCommunity::new(0).as_mac_mobility(), None);
1634 }
1635
1636 #[test]
1637 fn ext_comm_esi_label_flags_and_label() {
1638 let e1 = ExtendedCommunity::esi_label(false, 10_000);
1639 assert_eq!(e1.as_esi_label(), Some((false, 10_000)));
1640 let e2 = ExtendedCommunity::esi_label(true, 0x00FF_FFFF);
1641 assert_eq!(e2.as_esi_label(), Some((true, 0x00FF_FFFF)));
1642 }
1643
1644 #[test]
1645 fn ext_comm_es_import_rt_mac() {
1646 let mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
1647 let e = ExtendedCommunity::es_import_rt(mac);
1648 assert_eq!(e.as_es_import_rt(), Some(mac));
1649 assert_eq!(e.type_byte(), 0x06);
1650 assert_eq!(e.subtype(), 0x02);
1651 }
1652
1653 #[test]
1654 fn ext_comm_router_mac() {
1655 let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
1656 let e = ExtendedCommunity::router_mac(mac);
1657 assert_eq!(e.as_router_mac(), Some(mac));
1658 }
1659
1660 #[test]
1661 fn ext_comm_default_gateway_flag_only() {
1662 let d = ExtendedCommunity::default_gateway();
1663 assert!(d.as_default_gateway());
1664 assert!(!ExtendedCommunity::bgp_encapsulation(8).as_default_gateway());
1666 }
1667
1668 #[test]
1672 fn ext_comm_default_gateway_rejects_nonzero_value() {
1673 let malformed =
1675 ExtendedCommunity::new(u64::from_be_bytes([0x03, 0x0D, 0, 0, 0, 0, 0, 0x01]));
1676 assert!(
1677 !malformed.as_default_gateway(),
1678 "default-gateway accessor must require all-zero value bytes"
1679 );
1680 assert!(ExtendedCommunity::default_gateway().as_default_gateway());
1682 }
1683
1684 #[test]
1685 fn ext_comm_accessors_return_none_on_unrelated_communities() {
1686 let rt = ExtendedCommunity::new(u64::from_be_bytes([0x00, 0x02, 0xFD, 0xE8, 0, 0, 0, 100])); assert_eq!(rt.as_bgp_encapsulation(), None);
1688 assert_eq!(rt.as_mac_mobility(), None);
1689 assert_eq!(rt.as_esi_label(), None);
1690 assert_eq!(rt.as_es_import_rt(), None);
1691 assert_eq!(rt.as_router_mac(), None);
1692 assert!(!rt.as_default_gateway());
1693 }
1694
1695 #[test]
1696 fn origin_from_u8_roundtrip() {
1697 assert_eq!(Origin::from_u8(0), Some(Origin::Igp));
1698 assert_eq!(Origin::from_u8(1), Some(Origin::Egp));
1699 assert_eq!(Origin::from_u8(2), Some(Origin::Incomplete));
1700 assert_eq!(Origin::from_u8(3), None);
1701 }
1702
1703 #[test]
1704 fn origin_ordering() {
1705 assert!(Origin::Igp < Origin::Egp);
1706 assert!(Origin::Egp < Origin::Incomplete);
1707 }
1708
1709 #[test]
1710 fn as_path_length_calculation() {
1711 let path = AsPath {
1712 segments: vec![
1713 AsPathSegment::AsSequence(vec![65001, 65002, 65003]),
1714 AsPathSegment::AsSet(vec![65004, 65005]),
1715 ],
1716 };
1717 assert_eq!(path.len(), 4);
1719 }
1720
1721 #[test]
1722 fn as_path_empty() {
1723 let path = AsPath { segments: vec![] };
1724 assert!(path.is_empty());
1725 assert_eq!(path.len(), 0);
1726 }
1727
1728 #[test]
1729 fn contains_asn_in_sequence() {
1730 let path = AsPath {
1731 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
1732 };
1733 assert!(path.contains_asn(65002));
1734 assert!(!path.contains_asn(65004));
1735 }
1736
1737 #[test]
1738 fn contains_asn_in_set() {
1739 let path = AsPath {
1740 segments: vec![AsPathSegment::AsSet(vec![65004, 65005])],
1741 };
1742 assert!(path.contains_asn(65005));
1743 assert!(!path.contains_asn(65001));
1744 }
1745
1746 #[test]
1747 fn contains_asn_multiple_segments() {
1748 let path = AsPath {
1749 segments: vec![
1750 AsPathSegment::AsSequence(vec![65001, 65002]),
1751 AsPathSegment::AsSet(vec![65003]),
1752 ],
1753 };
1754 assert!(path.contains_asn(65001));
1755 assert!(path.contains_asn(65003));
1756 assert!(!path.contains_asn(65004));
1757 }
1758
1759 #[test]
1760 fn contains_asn_empty_path() {
1761 let path = AsPath { segments: vec![] };
1762 assert!(!path.contains_asn(65001));
1763 }
1764
1765 #[test]
1766 fn is_private_asn_boundaries() {
1767 assert!(!is_private_asn(64_511));
1769 assert!(is_private_asn(64_512));
1770 assert!(is_private_asn(65_534));
1771 assert!(!is_private_asn(65_535));
1772
1773 assert!(!is_private_asn(4_199_999_999));
1775 assert!(is_private_asn(4_200_000_000));
1776 assert!(is_private_asn(4_294_967_294));
1777 assert!(!is_private_asn(4_294_967_295));
1778 }
1779
1780 #[test]
1781 fn all_private_empty_path_is_false() {
1782 let path = AsPath { segments: vec![] };
1783 assert!(!path.all_private());
1784 }
1785
1786 #[test]
1787 fn all_private_mixed_segments() {
1788 let path = AsPath {
1789 segments: vec![
1790 AsPathSegment::AsSet(vec![64_512, 65_000]),
1791 AsPathSegment::AsSequence(vec![4_200_000_000, 65_534]),
1792 ],
1793 };
1794 assert!(path.all_private());
1795
1796 let non_private = AsPath {
1797 segments: vec![
1798 AsPathSegment::AsSet(vec![64_512, 65_000]),
1799 AsPathSegment::AsSequence(vec![65_535]),
1800 ],
1801 };
1802 assert!(!non_private.all_private());
1803 }
1804
1805 #[test]
1806 fn decode_origin_igp() {
1807 let buf = [0x40, 0x01, 0x01, 0x00];
1809 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1810 assert_eq!(attrs.len(), 1);
1811 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1812 }
1813
1814 #[test]
1815 fn decode_origin_egp() {
1816 let buf = [0x40, 0x01, 0x01, 0x01];
1817 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1818 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Egp));
1819 }
1820
1821 #[test]
1822 fn decode_origin_invalid_value() {
1823 let buf = [0x40, 0x01, 0x01, 0x05];
1825 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
1826 match &err {
1827 DecodeError::UpdateAttributeError { subcode, .. } => {
1828 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
1829 }
1830 other => panic!("expected UpdateAttributeError, got: {other:?}"),
1831 }
1832 }
1833
1834 #[test]
1835 fn decode_next_hop() {
1836 let buf = [0x40, 0x03, 0x04, 10, 0, 0, 1];
1838 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1839 assert_eq!(attrs[0], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1840 }
1841
1842 #[test]
1843 fn decode_med() {
1844 let buf = [0x80, 0x04, 0x04, 0, 0, 0, 100];
1846 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1847 assert_eq!(attrs[0], PathAttribute::Med(100));
1848 }
1849
1850 #[test]
1851 fn decode_local_pref() {
1852 let buf = [0x40, 0x05, 0x04, 0, 0, 0, 200];
1854 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1855 assert_eq!(attrs[0], PathAttribute::LocalPref(200));
1856 }
1857
1858 #[test]
1859 fn decode_as_path_4byte() {
1860 let buf = [
1863 0x40, 0x02, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1868 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1869 assert_eq!(
1870 attrs[0],
1871 PathAttribute::AsPath(AsPath {
1872 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1873 })
1874 );
1875 }
1876
1877 #[test]
1878 fn decode_as_path_2byte() {
1879 let buf = [
1882 0x40, 0x02, 0x06, 0x02, 0x02, 0x00, 0x64, 0x00, 0xC8, ];
1887 let attrs = decode_path_attributes(&buf, false, &[]).unwrap();
1888 assert_eq!(
1889 attrs[0],
1890 PathAttribute::AsPath(AsPath {
1891 segments: vec![AsPathSegment::AsSequence(vec![100, 200])]
1892 })
1893 );
1894 }
1895
1896 #[test]
1897 fn decode_unknown_attribute_preserved() {
1898 let buf = [0xC0, 99, 0x03, 1, 2, 3];
1900 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1901 assert_eq!(
1902 attrs[0],
1903 PathAttribute::Unknown(RawAttribute {
1904 flags: 0xC0,
1905 type_code: 99,
1906 data: Bytes::from_static(&[1, 2, 3]),
1907 })
1908 );
1909 }
1910
1911 #[test]
1912 fn decode_atomic_aggregate_as_unknown() {
1913 let buf = [0x40, 0x06, 0x00];
1915 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1916 assert!(matches!(attrs[0], PathAttribute::Unknown(_)));
1917 }
1918
1919 #[test]
1920 fn decode_extended_length() {
1921 let buf = [
1924 0x50, 0x02, 0x00, 0x0A, 0x02, 0x02, 0x00, 0x00, 0xFD, 0xE9, 0x00, 0x00, 0xFD, 0xEA, ];
1929 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1930 assert_eq!(
1931 attrs[0],
1932 PathAttribute::AsPath(AsPath {
1933 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])]
1934 })
1935 );
1936 }
1937
1938 #[test]
1939 fn decode_multiple_attributes() {
1940 let mut buf = Vec::new();
1941 buf.extend_from_slice(&[0x40, 0x01, 0x01, 0x00]);
1943 buf.extend_from_slice(&[0x40, 0x03, 0x04, 10, 0, 0, 1]);
1945 buf.extend_from_slice(&[0x40, 0x02, 0x00]);
1947
1948 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
1949 assert_eq!(attrs.len(), 3);
1950 assert_eq!(attrs[0], PathAttribute::Origin(Origin::Igp));
1951 assert_eq!(attrs[1], PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)));
1952 assert_eq!(attrs[2], PathAttribute::AsPath(AsPath { segments: vec![] }));
1953 }
1954
1955 #[test]
1956 fn roundtrip_attributes_4byte() {
1957 let attrs = vec![
1958 PathAttribute::Origin(Origin::Igp),
1959 PathAttribute::AsPath(AsPath {
1960 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002])],
1961 }),
1962 PathAttribute::NextHop(Ipv4Addr::new(10, 0, 0, 1)),
1963 PathAttribute::Med(100),
1964 PathAttribute::LocalPref(200),
1965 ];
1966
1967 let mut buf = Vec::new();
1968 encode_path_attributes(&attrs, &mut buf, true, false);
1969 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
1970 assert_eq!(decoded, attrs);
1971 }
1972
1973 #[test]
1974 fn roundtrip_attributes_2byte() {
1975 let attrs = vec![
1976 PathAttribute::Origin(Origin::Egp),
1977 PathAttribute::AsPath(AsPath {
1978 segments: vec![AsPathSegment::AsSequence(vec![100, 200])],
1979 }),
1980 PathAttribute::NextHop(Ipv4Addr::new(172, 16, 0, 1)),
1981 ];
1982
1983 let mut buf = Vec::new();
1984 encode_path_attributes(&attrs, &mut buf, false, false);
1985 let decoded = decode_path_attributes(&buf, false, &[]).unwrap();
1986 assert_eq!(decoded, attrs);
1987 }
1988
1989 #[test]
1990 fn reject_truncated_attribute_header() {
1991 let buf = [0x40]; assert!(decode_path_attributes(&buf, true, &[]).is_err());
1993 }
1994
1995 #[test]
1996 fn reject_truncated_attribute_value() {
1997 let buf = [0x40, 0x01, 0x01];
1999 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2000 }
2001
2002 #[test]
2003 fn reject_bad_origin_length() {
2004 let buf = [0x40, 0x01, 0x02, 0x00, 0x00];
2006 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2007 }
2008
2009 #[test]
2010 fn as_path_with_set_and_sequence() {
2011 let attrs = vec![PathAttribute::AsPath(AsPath {
2013 segments: vec![
2014 AsPathSegment::AsSequence(vec![65001]),
2015 AsPathSegment::AsSet(vec![65002, 65003]),
2016 ],
2017 })];
2018
2019 let mut buf = Vec::new();
2020 encode_path_attributes(&attrs, &mut buf, true, false);
2021 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2022 assert_eq!(decoded, attrs);
2023 }
2024
2025 #[test]
2026 fn decode_communities_single() {
2027 let community: u32 = (65001 << 16) | 0x0064;
2030 let bytes = community.to_be_bytes();
2031 let buf = [0xC0, 0x08, 0x04, bytes[0], bytes[1], bytes[2], bytes[3]];
2032 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2033 assert_eq!(attrs.len(), 1);
2034 assert_eq!(attrs[0], PathAttribute::Communities(vec![community]));
2035 }
2036
2037 #[test]
2038 fn decode_communities_multiple() {
2039 let c1: u32 = (65001 << 16) | 0x0064;
2040 let c2: u32 = (65002 << 16) | 0x00C8;
2041 let b1 = c1.to_be_bytes();
2042 let b2 = c2.to_be_bytes();
2043 let buf = [
2044 0xC0, 0x08, 0x08, b1[0], b1[1], b1[2], b1[3], b2[0], b2[1], b2[2], b2[3],
2045 ];
2046 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2047 assert_eq!(attrs[0], PathAttribute::Communities(vec![c1, c2]));
2048 }
2049
2050 #[test]
2051 fn decode_communities_empty() {
2052 let buf = [0xC0, 0x08, 0x00];
2054 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2055 assert_eq!(attrs[0], PathAttribute::Communities(vec![]));
2056 }
2057
2058 #[test]
2059 fn decode_communities_odd_length_rejected() {
2060 let buf = [0xC0, 0x08, 0x03, 0x01, 0x02, 0x03];
2062 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2063 }
2064
2065 #[test]
2066 fn communities_roundtrip() {
2067 let c1: u32 = (65001 << 16) | 0x0064;
2068 let c2: u32 = (65002 << 16) | 0x00C8;
2069 let attrs = vec![PathAttribute::Communities(vec![c1, c2])];
2070
2071 let mut buf = Vec::new();
2072 encode_path_attributes(&attrs, &mut buf, true, false);
2073 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2074 assert_eq!(decoded, attrs);
2075 }
2076
2077 #[test]
2078 fn communities_type_code_and_flags() {
2079 let attr = PathAttribute::Communities(vec![]);
2080 assert_eq!(attr.type_code(), 8);
2081 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2082 }
2083
2084 #[test]
2087 fn decode_extended_communities_single() {
2088 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2090 let bytes = ec.as_u64().to_be_bytes();
2091 let buf = [
2092 0xC0, 0x10, 0x08, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
2093 bytes[7],
2094 ];
2095 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2096 assert_eq!(attrs.len(), 1);
2097 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec]));
2098 }
2099
2100 #[test]
2101 fn decode_extended_communities_multiple() {
2102 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2103 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2104 let b1 = ec1.as_u64().to_be_bytes();
2105 let b2 = ec2.as_u64().to_be_bytes();
2106 let mut buf = vec![0xC0, 0x10, 16]; buf.extend_from_slice(&b1);
2108 buf.extend_from_slice(&b2);
2109 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2110 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![ec1, ec2]));
2111 }
2112
2113 #[test]
2114 fn decode_extended_communities_empty() {
2115 let buf = [0xC0, 0x10, 0x00];
2116 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2117 assert_eq!(attrs[0], PathAttribute::ExtendedCommunities(vec![]));
2118 }
2119
2120 #[test]
2121 fn decode_extended_communities_bad_length() {
2122 let buf = [0xC0, 0x10, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
2124 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2125 }
2126
2127 #[test]
2128 fn extended_communities_roundtrip() {
2129 let ec1 = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2130 let ec2 = ExtendedCommunity::new(0x0003_FDEA_0000_00C8);
2131 let attrs = vec![PathAttribute::ExtendedCommunities(vec![ec1, ec2])];
2132
2133 let mut buf = Vec::new();
2134 encode_path_attributes(&attrs, &mut buf, true, false);
2135 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2136 assert_eq!(decoded, attrs);
2137 }
2138
2139 #[test]
2140 fn extended_communities_type_code_and_flags() {
2141 let attr = PathAttribute::ExtendedCommunities(vec![]);
2142 assert_eq!(attr.type_code(), 16);
2143 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2144 }
2145
2146 #[test]
2147 fn extended_community_type_subtype() {
2148 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2150 assert_eq!(ec.type_byte(), 0x00);
2151 assert_eq!(ec.subtype(), 0x02);
2152 assert!(ec.is_transitive());
2153 }
2154
2155 #[test]
2156 fn extended_community_route_target() {
2157 let ec = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2159 assert_eq!(ec.route_target(), Some((65001, 100)));
2160 assert_eq!(ec.route_origin(), None);
2161
2162 let ec4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2164 assert_eq!(ec4.route_target(), Some((65537, 200)));
2165
2166 let ec_ipv4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2169 let (g, l) = ec_ipv4.route_target().unwrap();
2170 assert_eq!(g, 0xC000_0201); assert_eq!(l, 100);
2172 assert_eq!(ec_ipv4.type_byte() & 0x3F, 0x01);
2174 }
2175
2176 #[test]
2177 fn extended_community_is_transitive() {
2178 let t = ExtendedCommunity::new(0x0002_0000_0000_0000);
2180 assert!(t.is_transitive());
2181
2182 let nt = ExtendedCommunity::new(0x4002_0000_0000_0000);
2184 assert!(!nt.is_transitive());
2185 }
2186
2187 #[test]
2188 fn extended_community_display() {
2189 let rt = ExtendedCommunity::new(0x0002_FDE9_0000_0064);
2190 assert_eq!(rt.to_string(), "RT:65001:100");
2191
2192 let ro = ExtendedCommunity::new(0x0003_FDE9_0000_0064);
2193 assert_eq!(ro.to_string(), "RO:65001:100");
2194
2195 let target_v4 = ExtendedCommunity::new(0x0102_C000_0201_0064);
2197 assert_eq!(target_v4.to_string(), "RT:192.0.2.1:100");
2198
2199 let origin_v4 = ExtendedCommunity::new(0x0103_C000_0201_0064);
2201 assert_eq!(origin_v4.to_string(), "RO:192.0.2.1:100");
2202
2203 let rt_as4 = ExtendedCommunity::new(0x0202_0001_0001_00C8);
2205 assert_eq!(rt_as4.to_string(), "RT:65537:200");
2206
2207 let opaque = ExtendedCommunity::new(0x4300_1234_5678_9ABC);
2209 assert_eq!(opaque.to_string(), "0x4300123456789abc");
2210 }
2211
2212 #[test]
2213 fn unknown_attribute_roundtrip() {
2214 let attrs = vec![PathAttribute::Unknown(RawAttribute {
2217 flags: 0xC0,
2218 type_code: 99,
2219 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2220 })];
2221
2222 let mut buf = Vec::new();
2223 encode_path_attributes(&attrs, &mut buf, true, false);
2224 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2225 assert_eq!(
2226 decoded,
2227 vec![PathAttribute::Unknown(RawAttribute {
2228 flags: 0xE0, type_code: 99,
2230 data: Bytes::from_static(&[1, 2, 3, 4, 5]),
2231 })]
2232 );
2233 }
2234
2235 #[test]
2236 fn origin_with_optional_flag_rejected() {
2237 let buf = [0xC0, 0x01, 0x01, 0x00];
2239 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2240 match &err {
2241 DecodeError::UpdateAttributeError { subcode, .. } => {
2242 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2243 }
2244 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2245 }
2246 }
2247
2248 #[test]
2249 fn med_with_transitive_flag_rejected() {
2250 let buf = [0xC0, 0x04, 0x04, 0, 0, 0, 100];
2252 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2253 match &err {
2254 DecodeError::UpdateAttributeError { subcode, .. } => {
2255 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2256 }
2257 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2258 }
2259 }
2260
2261 #[test]
2262 fn communities_without_optional_rejected() {
2263 let buf = [0x40, 0x08, 0x04, 0, 0, 0, 100];
2265 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2266 match &err {
2267 DecodeError::UpdateAttributeError { subcode, .. } => {
2268 assert_eq!(*subcode, update_subcode::ATTRIBUTE_FLAGS_ERROR);
2269 }
2270 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2271 }
2272 }
2273
2274 #[test]
2275 fn next_hop_length_error_subcode() {
2276 let buf = [0x40, 0x03, 0x03, 10, 0, 0];
2278 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2279 match &err {
2280 DecodeError::UpdateAttributeError { subcode, .. } => {
2281 assert_eq!(*subcode, update_subcode::ATTRIBUTE_LENGTH_ERROR);
2282 }
2283 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2284 }
2285 }
2286
2287 #[test]
2288 fn invalid_origin_value_subcode() {
2289 let buf = [0x40, 0x01, 0x01, 0x05];
2291 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2292 match &err {
2293 DecodeError::UpdateAttributeError { subcode, .. } => {
2294 assert_eq!(*subcode, update_subcode::INVALID_ORIGIN);
2295 }
2296 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2297 }
2298 }
2299
2300 #[test]
2301 fn as_path_bad_segment_subcode() {
2302 let buf = [
2304 0x40, 0x02, 0x06, 0x05, 0x01, 0x00, 0x00, 0xFD, 0xE9, ];
2308 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2309 match &err {
2310 DecodeError::UpdateAttributeError { subcode, .. } => {
2311 assert_eq!(*subcode, update_subcode::MALFORMED_AS_PATH);
2312 }
2313 other => panic!("expected UpdateAttributeError, got: {other:?}"),
2314 }
2315 }
2316
2317 #[test]
2318 fn encode_unknown_transitive_sets_partial() {
2319 let attr = PathAttribute::Unknown(RawAttribute {
2320 flags: attr_flags::OPTIONAL | attr_flags::TRANSITIVE, type_code: 99,
2322 data: Bytes::from_static(&[1, 2]),
2323 });
2324 let mut buf = Vec::new();
2325 encode_path_attributes(&[attr], &mut buf, true, false);
2326 assert_eq!(
2328 buf[0],
2329 attr_flags::OPTIONAL | attr_flags::TRANSITIVE | attr_flags::PARTIAL
2330 );
2331 }
2332
2333 #[test]
2334 fn encode_unknown_wellknown_transitive_no_partial() {
2335 let attr = PathAttribute::Unknown(RawAttribute {
2337 flags: attr_flags::TRANSITIVE, type_code: 99,
2339 data: Bytes::from_static(&[1, 2]),
2340 });
2341 let mut buf = Vec::new();
2342 encode_path_attributes(&[attr], &mut buf, true, false);
2343 assert_eq!(buf[0], attr_flags::TRANSITIVE);
2344 }
2345
2346 #[test]
2347 fn encode_unknown_nontransitive_no_partial() {
2348 let attr = PathAttribute::Unknown(RawAttribute {
2349 flags: attr_flags::OPTIONAL, type_code: 99,
2351 data: Bytes::from_static(&[1, 2]),
2352 });
2353 let mut buf = Vec::new();
2354 encode_path_attributes(&[attr], &mut buf, true, false);
2355 assert_eq!(buf[0], attr_flags::OPTIONAL);
2357 }
2358
2359 fn nlri(prefix: Prefix) -> NlriEntry {
2363 NlriEntry { path_id: 0, prefix }
2364 }
2365
2366 #[test]
2367 fn mp_reach_nlri_ipv6_roundtrip() {
2368 use crate::capability::{Afi, Safi};
2369 use crate::nlri::{Ipv6Prefix, Prefix};
2370
2371 let mp = MpReachNlri {
2372 afi: Afi::Ipv6,
2373 safi: Safi::Unicast,
2374 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2375 link_local_next_hop: None,
2376 announced: vec![
2377 nlri(Prefix::V6(Ipv6Prefix::new(
2378 "2001:db8:1::".parse().unwrap(),
2379 48,
2380 ))),
2381 nlri(Prefix::V6(Ipv6Prefix::new(
2382 "2001:db8:2::".parse().unwrap(),
2383 48,
2384 ))),
2385 ],
2386 flowspec_announced: vec![],
2387 evpn_announced: vec![],
2388 };
2389 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2390
2391 let mut buf = Vec::new();
2392 encode_path_attributes(&attrs, &mut buf, true, false);
2393 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2394 assert_eq!(decoded.len(), 1);
2395 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2396 }
2397
2398 #[test]
2399 fn mp_unreach_nlri_ipv6_roundtrip() {
2400 use crate::capability::{Afi, Safi};
2401 use crate::nlri::{Ipv6Prefix, Prefix};
2402
2403 let mp = MpUnreachNlri {
2404 afi: Afi::Ipv6,
2405 safi: Safi::Unicast,
2406 withdrawn: vec![nlri(Prefix::V6(Ipv6Prefix::new(
2407 "2001:db8:1::".parse().unwrap(),
2408 48,
2409 )))],
2410 flowspec_withdrawn: vec![],
2411 evpn_withdrawn: vec![],
2412 };
2413 let attrs = vec![PathAttribute::MpUnreachNlri(mp.clone())];
2414
2415 let mut buf = Vec::new();
2416 encode_path_attributes(&attrs, &mut buf, true, false);
2417 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2418 assert_eq!(decoded.len(), 1);
2419 assert_eq!(decoded[0], PathAttribute::MpUnreachNlri(mp));
2420 }
2421
2422 #[test]
2423 fn mp_reach_nlri_ipv4_roundtrip() {
2424 use crate::capability::{Afi, Safi};
2425 use crate::nlri::Prefix;
2426
2427 let mp = MpReachNlri {
2428 afi: Afi::Ipv4,
2429 safi: Safi::Unicast,
2430 next_hop: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
2431 link_local_next_hop: None,
2432 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2433 Ipv4Addr::new(10, 1, 0, 0),
2434 16,
2435 )))],
2436 flowspec_announced: vec![],
2437 evpn_announced: vec![],
2438 };
2439 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2440
2441 let mut buf = Vec::new();
2442 encode_path_attributes(&attrs, &mut buf, true, false);
2443 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2444 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2445 }
2446
2447 #[test]
2448 fn mp_reach_nlri_ipv4_with_ipv6_nexthop_roundtrip() {
2449 use crate::capability::{Afi, Safi};
2450 use crate::nlri::Prefix;
2451
2452 let mp = MpReachNlri {
2453 afi: Afi::Ipv4,
2454 safi: Safi::Unicast,
2455 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2456 link_local_next_hop: None,
2457 announced: vec![nlri(Prefix::V4(crate::nlri::Ipv4Prefix::new(
2458 Ipv4Addr::new(10, 1, 0, 0),
2459 16,
2460 )))],
2461 flowspec_announced: vec![],
2462 evpn_announced: vec![],
2463 };
2464 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2465
2466 let mut buf = Vec::new();
2467 encode_path_attributes(&attrs, &mut buf, true, false);
2468 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2469 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2470 }
2471
2472 #[test]
2473 fn mp_reach_nlri_type_code_and_flags() {
2474 use crate::capability::{Afi, Safi};
2475
2476 let attr = PathAttribute::MpReachNlri(MpReachNlri {
2477 afi: Afi::Ipv6,
2478 safi: Safi::Unicast,
2479 next_hop: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
2480 link_local_next_hop: None,
2481 announced: vec![],
2482 flowspec_announced: vec![],
2483 evpn_announced: vec![],
2484 });
2485 assert_eq!(attr.type_code(), 14);
2486 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2488 }
2489
2490 #[test]
2491 fn mp_unreach_nlri_type_code_and_flags() {
2492 use crate::capability::{Afi, Safi};
2493
2494 let attr = PathAttribute::MpUnreachNlri(MpUnreachNlri {
2495 afi: Afi::Ipv6,
2496 safi: Safi::Unicast,
2497 withdrawn: vec![],
2498 flowspec_withdrawn: vec![],
2499 evpn_withdrawn: vec![],
2500 });
2501 assert_eq!(attr.type_code(), 15);
2502 assert_eq!(attr.flags(), attr_flags::OPTIONAL);
2503 }
2504
2505 #[test]
2506 fn mp_reach_nlri_empty_nlri() {
2507 use crate::capability::{Afi, Safi};
2508
2509 let mp = MpReachNlri {
2510 afi: Afi::Ipv6,
2511 safi: Safi::Unicast,
2512 next_hop: IpAddr::V6("fe80::1".parse().unwrap()),
2513 link_local_next_hop: None,
2514 announced: vec![],
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[0], PathAttribute::MpReachNlri(mp));
2524 }
2525
2526 #[test]
2527 fn mp_reach_nlri_bad_flags_rejected() {
2528 let mut value = Vec::new();
2532 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();
2539 buf.push(0x40); buf.push(14); #[expect(clippy::cast_possible_truncation)]
2542 buf.push(value.len() as u8);
2543 buf.extend_from_slice(&value);
2544
2545 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2546 assert!(matches!(
2547 err,
2548 DecodeError::UpdateAttributeError {
2549 subcode: 4, ..
2551 }
2552 ));
2553 }
2554
2555 #[test]
2558 #[expect(clippy::cast_possible_truncation)]
2559 fn mp_reach_nlri_ipv4_addpath_decode() {
2560 use crate::capability::{Afi, Safi};
2561 use crate::nlri::Prefix;
2562
2563 let mut value = Vec::new();
2566 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());
2573 value.push(16);
2574 value.extend_from_slice(&[10, 1]);
2575
2576 let mut buf = Vec::new();
2577 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2580 buf.extend_from_slice(&value);
2581
2582 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2584 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2585 panic!("expected MpReachNlri");
2586 };
2587 assert_eq!(mp.announced.len(), 1);
2588 assert_eq!(mp.announced[0].path_id, 42);
2589 assert!(matches!(mp.announced[0].prefix, Prefix::V4(p) if p.len == 16));
2590
2591 assert!(decode_path_attributes(&buf, true, &[]).is_err());
2594 }
2595
2596 #[test]
2597 #[expect(clippy::cast_possible_truncation)]
2598 fn mp_reach_nlri_ipv6_addpath_decode() {
2599 use crate::capability::{Afi, Safi};
2600 use crate::nlri::{Ipv6Prefix, Prefix};
2601
2602 let mut value = Vec::new();
2604 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());
2608 value.push(0); value.extend_from_slice(&99u32.to_be_bytes());
2611 value.push(48);
2612 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01]);
2613
2614 let mut buf = Vec::new();
2615 buf.push(0x90); buf.push(14); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2618 buf.extend_from_slice(&value);
2619
2620 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2621 let PathAttribute::MpReachNlri(mp) = &decoded[0] else {
2622 panic!("expected MpReachNlri");
2623 };
2624 assert_eq!(mp.announced.len(), 1);
2625 assert_eq!(mp.announced[0].path_id, 99);
2626 assert_eq!(
2627 mp.announced[0].prefix,
2628 Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48))
2629 );
2630 }
2631
2632 #[test]
2633 #[expect(clippy::cast_possible_truncation)]
2634 fn mp_unreach_nlri_ipv6_addpath_decode() {
2635 use crate::capability::{Afi, Safi};
2636 use crate::nlri::{Ipv6Prefix, Prefix};
2637
2638 let mut value = Vec::new();
2640 value.extend_from_slice(&2u16.to_be_bytes()); value.push(1); value.extend_from_slice(&7u32.to_be_bytes());
2644 value.push(48);
2645 value.extend_from_slice(&[0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02]);
2646
2647 let mut buf = Vec::new();
2648 buf.push(0x90); buf.push(15); buf.extend_from_slice(&(value.len() as u16).to_be_bytes());
2651 buf.extend_from_slice(&value);
2652
2653 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv6, Safi::Unicast)]).unwrap();
2654 let PathAttribute::MpUnreachNlri(mp) = &decoded[0] else {
2655 panic!("expected MpUnreachNlri");
2656 };
2657 assert_eq!(mp.withdrawn.len(), 1);
2658 assert_eq!(mp.withdrawn[0].path_id, 7);
2659 assert_eq!(
2660 mp.withdrawn[0].prefix,
2661 Prefix::V6(Ipv6Prefix::new("2001:db8:2::".parse().unwrap(), 48))
2662 );
2663 }
2664
2665 #[test]
2666 fn mp_reach_addpath_only_applies_to_matching_family() {
2667 use crate::capability::{Afi, Safi};
2668 use crate::nlri::{Ipv6Prefix, Prefix};
2669
2670 let mp = MpReachNlri {
2672 afi: Afi::Ipv6,
2673 safi: Safi::Unicast,
2674 next_hop: IpAddr::V6("2001:db8::1".parse().unwrap()),
2675 link_local_next_hop: None,
2676 announced: vec![NlriEntry {
2677 path_id: 0,
2678 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2679 }],
2680 flowspec_announced: vec![],
2681 evpn_announced: vec![],
2682 };
2683 let attrs = vec![PathAttribute::MpReachNlri(mp.clone())];
2684
2685 let mut buf = Vec::new();
2686 encode_path_attributes(&attrs, &mut buf, true, false);
2687
2688 let decoded = decode_path_attributes(&buf, true, &[(Afi::Ipv4, Safi::Unicast)]).unwrap();
2690 assert_eq!(decoded[0], PathAttribute::MpReachNlri(mp));
2691 }
2692
2693 #[test]
2696 fn decode_originator_id() {
2697 let buf = [0x80, 0x09, 0x04, 1, 2, 3, 4];
2699 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2700 assert_eq!(
2701 attrs[0],
2702 PathAttribute::OriginatorId(Ipv4Addr::new(1, 2, 3, 4))
2703 );
2704 }
2705
2706 #[test]
2711 fn mp_reach_ipv6_32byte_next_hop_roundtrip() {
2712 use crate::capability::{Afi, Safi};
2713 use crate::nlri::{Ipv6Prefix, Prefix};
2714 let global: Ipv6Addr = "2001:db8::1".parse().unwrap();
2715 let link_local: Ipv6Addr = "fe80::1".parse().unwrap();
2716 let mp = MpReachNlri {
2717 afi: Afi::Ipv6,
2718 safi: Safi::Unicast,
2719 next_hop: IpAddr::V6(global),
2720 link_local_next_hop: Some(link_local),
2721 announced: vec![NlriEntry {
2722 path_id: 0,
2723 prefix: Prefix::V6(Ipv6Prefix::new("2001:db8:1::".parse().unwrap(), 48)),
2724 }],
2725 flowspec_announced: vec![],
2726 evpn_announced: vec![],
2727 };
2728 let attr = PathAttribute::MpReachNlri(mp.clone());
2729 let mut buf = Vec::new();
2730 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2731
2732 let extended = (buf[0] & 0x10) != 0;
2736 let value_off = if extended { 4 } else { 3 };
2737 assert_eq!(buf[value_off + 3], 32, "NH-Len must be 32 for global+LL");
2739 assert_eq!(&buf[value_off + 4..value_off + 20], &global.octets());
2740 assert_eq!(
2741 &buf[value_off + 20..value_off + 36],
2742 &link_local.octets(),
2743 "encoded link-local bytes must match the input"
2744 );
2745
2746 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2747 let PathAttribute::MpReachNlri(dec) = &decoded[0] else {
2748 panic!("expected MpReachNlri");
2749 };
2750 assert_eq!(dec.next_hop, IpAddr::V6(global));
2751 assert_eq!(dec.link_local_next_hop, Some(link_local));
2752 }
2753
2754 #[test]
2755 fn originator_id_roundtrip() {
2756 let attr = PathAttribute::OriginatorId(Ipv4Addr::new(10, 0, 0, 1));
2757 let mut buf = Vec::new();
2758 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2759 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2760 assert_eq!(decoded, vec![attr]);
2761 }
2762
2763 #[test]
2764 fn originator_id_wrong_length() {
2765 let buf = [0x80, 0x09, 0x03, 1, 2, 3];
2767 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2768 assert!(matches!(
2769 err,
2770 DecodeError::UpdateAttributeError {
2771 subcode: 5, ..
2773 }
2774 ));
2775 }
2776
2777 #[test]
2778 fn originator_id_wrong_flags() {
2779 let buf = [0x40, 0x09, 0x04, 1, 2, 3, 4];
2781 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2782 assert!(matches!(
2783 err,
2784 DecodeError::UpdateAttributeError {
2785 subcode: 4, ..
2787 }
2788 ));
2789 }
2790
2791 #[test]
2794 fn decode_cluster_list() {
2795 let buf = [0x80, 0x0A, 0x08, 1, 2, 3, 4, 5, 6, 7, 8];
2797 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2798 assert_eq!(
2799 attrs[0],
2800 PathAttribute::ClusterList(vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8),])
2801 );
2802 }
2803
2804 #[test]
2805 fn cluster_list_roundtrip() {
2806 let attr = PathAttribute::ClusterList(vec![
2807 Ipv4Addr::new(10, 0, 0, 1),
2808 Ipv4Addr::new(10, 0, 0, 2),
2809 ]);
2810 let mut buf = Vec::new();
2811 encode_path_attributes(std::slice::from_ref(&attr), &mut buf, true, false);
2812 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2813 assert_eq!(decoded, vec![attr]);
2814 }
2815
2816 #[test]
2817 fn cluster_list_wrong_length() {
2818 let buf = [0x80, 0x0A, 0x05, 1, 2, 3, 4, 5];
2820 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2821 assert!(matches!(
2822 err,
2823 DecodeError::UpdateAttributeError {
2824 subcode: 5, ..
2826 }
2827 ));
2828 }
2829
2830 #[test]
2835 fn large_community_display() {
2836 let lc = LargeCommunity::new(65001, 100, 200);
2837 assert_eq!(lc.to_string(), "65001:100:200");
2838 }
2839
2840 #[test]
2841 fn large_community_type_code_and_flags() {
2842 let attr = PathAttribute::LargeCommunities(vec![LargeCommunity::new(1, 2, 3)]);
2843 assert_eq!(attr.type_code(), attr_type::LARGE_COMMUNITIES);
2844 assert_eq!(attr.flags(), attr_flags::OPTIONAL | attr_flags::TRANSITIVE);
2845 }
2846
2847 #[test]
2848 fn decode_large_community_single() {
2849 let mut buf = vec![0xC0, 32, 12];
2851 buf.extend_from_slice(&65001u32.to_be_bytes());
2852 buf.extend_from_slice(&100u32.to_be_bytes());
2853 buf.extend_from_slice(&200u32.to_be_bytes());
2854 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2855 assert_eq!(attrs.len(), 1);
2856 assert_eq!(
2857 attrs[0],
2858 PathAttribute::LargeCommunities(vec![LargeCommunity::new(65001, 100, 200)])
2859 );
2860 }
2861
2862 #[test]
2863 fn decode_large_community_multiple() {
2864 let mut buf = vec![0xC0, 32, 24];
2866 for (g, l1, l2) in [(65001u32, 100u32, 200u32), (65002, 300, 400)] {
2867 buf.extend_from_slice(&g.to_be_bytes());
2868 buf.extend_from_slice(&l1.to_be_bytes());
2869 buf.extend_from_slice(&l2.to_be_bytes());
2870 }
2871 let attrs = decode_path_attributes(&buf, true, &[]).unwrap();
2872 assert_eq!(
2873 attrs[0],
2874 PathAttribute::LargeCommunities(vec![
2875 LargeCommunity::new(65001, 100, 200),
2876 LargeCommunity::new(65002, 300, 400),
2877 ])
2878 );
2879 }
2880
2881 #[test]
2882 fn decode_large_community_bad_length() {
2883 let buf = [0xC0, 32, 10, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0];
2885 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2886 assert!(matches!(
2887 err,
2888 DecodeError::UpdateAttributeError {
2889 subcode: 5, ..
2891 }
2892 ));
2893 }
2894
2895 #[test]
2896 fn decode_large_community_empty_rejected() {
2897 let buf = [0xC0, 32, 0];
2899 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2900 assert!(matches!(
2901 err,
2902 DecodeError::UpdateAttributeError {
2903 subcode: 5, ..
2905 }
2906 ));
2907 }
2908
2909 #[test]
2910 fn large_community_roundtrip() {
2911 let lcs = vec![
2912 LargeCommunity::new(65001, 100, 200),
2913 LargeCommunity::new(0, u32::MAX, 42),
2914 ];
2915 let attr = PathAttribute::LargeCommunities(lcs.clone());
2916 let mut buf = Vec::new();
2917 encode_path_attributes(&[attr], &mut buf, true, false);
2918 let decoded = decode_path_attributes(&buf, true, &[]).unwrap();
2919 assert_eq!(decoded.len(), 1);
2920 assert_eq!(decoded[0], PathAttribute::LargeCommunities(lcs));
2921 }
2922
2923 #[test]
2924 fn large_community_expected_flags_validated() {
2925 let mut buf = vec![0x40, 32, 12];
2927 buf.extend_from_slice(&1u32.to_be_bytes());
2928 buf.extend_from_slice(&2u32.to_be_bytes());
2929 buf.extend_from_slice(&3u32.to_be_bytes());
2930 let err = decode_path_attributes(&buf, true, &[]).unwrap_err();
2931 assert!(matches!(
2932 err,
2933 DecodeError::UpdateAttributeError {
2934 subcode: 4, ..
2936 }
2937 ));
2938 }
2939
2940 #[test]
2945 fn aspath_string_sequence() {
2946 let p = AsPath {
2947 segments: vec![AsPathSegment::AsSequence(vec![65001, 65002, 65003])],
2948 };
2949 assert_eq!(p.to_aspath_string(), "65001 65002 65003");
2950 }
2951
2952 #[test]
2953 fn aspath_string_set() {
2954 let p = AsPath {
2955 segments: vec![AsPathSegment::AsSet(vec![65003, 65004])],
2956 };
2957 assert_eq!(p.to_aspath_string(), "{65003 65004}");
2958 }
2959
2960 #[test]
2961 fn aspath_string_mixed() {
2962 let p = AsPath {
2963 segments: vec![
2964 AsPathSegment::AsSequence(vec![65001, 65002]),
2965 AsPathSegment::AsSet(vec![65003, 65004]),
2966 ],
2967 };
2968 assert_eq!(p.to_aspath_string(), "65001 65002 {65003 65004}");
2969 }
2970
2971 #[test]
2972 fn aspath_string_empty() {
2973 let p = AsPath { segments: vec![] };
2974 assert_eq!(p.to_aspath_string(), "");
2975 }
2976
2977 #[test]
2982 fn mp_reach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
2983 let bytes = vec![
2986 0x00, 0x01, 70, 4, 192, 0, 2, 1, 0, 3, 0, ];
2992 let err = decode_mp_reach_nlri(&bytes, &[]).unwrap_err();
2993 match err {
2994 DecodeError::MalformedField { detail, .. } => {
2995 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
2996 }
2997 other => panic!("expected MalformedField, got {other:?}"),
2998 }
2999 }
3000
3001 #[test]
3002 fn mp_unreach_nlri_rejects_evpn_safi_with_non_l2vpn_afi() {
3003 let bytes = vec![
3004 0x00, 0x02, 70, 3, 0, ];
3008 let err = decode_mp_unreach_nlri(&bytes, &[]).unwrap_err();
3009 match err {
3010 DecodeError::MalformedField { detail, .. } => {
3011 assert!(detail.contains("SAFI EVPN"), "unexpected detail: {detail}");
3012 }
3013 other => panic!("expected MalformedField, got {other:?}"),
3014 }
3015 }
3016}