1use std::fmt;
22use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
23
24use crate::error::DecodeError;
25use crate::nlri::{Ipv4Prefix, Ipv6Prefix};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct EthernetTagId(pub u32);
34
35impl EthernetTagId {
36 pub const MAX_ET: Self = Self(0xFFFF_FFFF);
38
39 #[must_use]
41 pub fn is_max_et(&self) -> bool {
42 self.0 == Self::MAX_ET.0
43 }
44}
45
46impl fmt::Display for EthernetTagId {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 if self.is_max_et() {
49 write!(f, "MAX_ET")
50 } else {
51 write!(f, "{}", self.0)
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
58pub struct MacAddress(pub [u8; 6]);
59
60impl MacAddress {
61 #[must_use]
63 pub const fn new(bytes: [u8; 6]) -> Self {
64 Self(bytes)
65 }
66
67 #[must_use]
69 pub const fn octets(&self) -> [u8; 6] {
70 self.0
71 }
72}
73
74impl fmt::Display for MacAddress {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 let o = &self.0;
77 write!(
78 f,
79 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
80 o[0], o[1], o[2], o[3], o[4], o[5]
81 )
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
92pub struct EthernetSegmentIdentifier(pub [u8; 10]);
93
94impl EthernetSegmentIdentifier {
95 pub const ZERO: Self = Self([0u8; 10]);
97
98 #[must_use]
100 pub const fn new(bytes: [u8; 10]) -> Self {
101 Self(bytes)
102 }
103
104 #[must_use]
106 pub const fn octets(&self) -> [u8; 10] {
107 self.0
108 }
109
110 #[must_use]
112 pub const fn esi_type(&self) -> u8 {
113 self.0[0]
114 }
115
116 #[must_use]
118 pub fn is_zero(&self) -> bool {
119 self.0.iter().all(|&b| b == 0)
120 }
121}
122
123impl fmt::Display for EthernetSegmentIdentifier {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 for (i, byte) in self.0.iter().enumerate() {
126 if i > 0 {
127 write!(f, ":")?;
128 }
129 write!(f, "{byte:02x}")?;
130 }
131 Ok(())
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
142pub struct RouteDistinguisher(pub [u8; 8]);
143
144impl RouteDistinguisher {
145 pub const ZERO: Self = Self([0u8; 8]);
147
148 #[must_use]
150 pub const fn new(bytes: [u8; 8]) -> Self {
151 Self(bytes)
152 }
153
154 #[must_use]
156 pub const fn octets(&self) -> [u8; 8] {
157 self.0
158 }
159
160 #[must_use]
162 pub fn rd_type(&self) -> u16 {
163 u16::from_be_bytes([self.0[0], self.0[1]])
164 }
165}
166
167impl fmt::Display for RouteDistinguisher {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 let b = &self.0;
175 match self.rd_type() {
176 0 => {
177 let asn = u16::from_be_bytes([b[2], b[3]]);
178 let assigned = u32::from_be_bytes([b[4], b[5], b[6], b[7]]);
179 write!(f, "{asn}:{assigned}")
180 }
181 1 => {
182 let ip = Ipv4Addr::new(b[2], b[3], b[4], b[5]);
183 let assigned = u16::from_be_bytes([b[6], b[7]]);
184 write!(f, "{ip}:{assigned}")
185 }
186 2 => {
187 let asn = u32::from_be_bytes([b[2], b[3], b[4], b[5]]);
188 let assigned = u16::from_be_bytes([b[6], b[7]]);
189 write!(f, "{asn}:{assigned}")
190 }
191 _ => {
192 write!(f, "0x")?;
193 for byte in b {
194 write!(f, "{byte:02x}")?;
195 }
196 Ok(())
197 }
198 }
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
205pub enum RouteDistinguisherParseError {
206 MissingColon,
209 InvalidAdministrator(String),
212 InvalidAssignedNumber(String),
215 AssignedNumberOutOfRange { value: u64, max: u64 },
218}
219
220impl fmt::Display for RouteDistinguisherParseError {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 match self {
223 Self::MissingColon => write!(
224 f,
225 "expected RD in form `<asn|ipv4>:<assigned>`, missing `:`"
226 ),
227 Self::InvalidAdministrator(s) => write!(
228 f,
229 "invalid RD administrator {s:?}: expected ASN (u32) or IPv4 address"
230 ),
231 Self::InvalidAssignedNumber(s) => {
232 write!(
233 f,
234 "invalid RD assigned number {s:?}: expected non-negative integer"
235 )
236 }
237 Self::AssignedNumberOutOfRange { value, max } => write!(
238 f,
239 "RD assigned number {value} exceeds maximum {max} for the inferred RD type"
240 ),
241 }
242 }
243}
244
245impl std::error::Error for RouteDistinguisherParseError {}
246
247impl std::str::FromStr for RouteDistinguisher {
248 type Err = RouteDistinguisherParseError;
249
250 fn from_str(s: &str) -> Result<Self, Self::Err> {
260 let (admin, assigned) = s
261 .split_once(':')
262 .ok_or(RouteDistinguisherParseError::MissingColon)?;
263 let assigned_u64 = parse_unsigned(assigned)?;
264
265 if let Ok(ipv4) = admin.parse::<Ipv4Addr>() {
266 let assigned_u16 = u16::try_from(assigned_u64).map_err(|_| {
268 RouteDistinguisherParseError::AssignedNumberOutOfRange {
269 value: assigned_u64,
270 max: u64::from(u16::MAX),
271 }
272 })?;
273 let mut bytes = [0u8; 8];
274 bytes[0..2].copy_from_slice(&1u16.to_be_bytes());
275 bytes[2..6].copy_from_slice(&ipv4.octets());
276 bytes[6..8].copy_from_slice(&assigned_u16.to_be_bytes());
277 return Ok(Self(bytes));
278 }
279
280 let asn = admin
281 .parse::<u32>()
282 .map_err(|_| RouteDistinguisherParseError::InvalidAdministrator(admin.to_string()))?;
283
284 if let Ok(asn_u16) = u16::try_from(asn) {
285 let assigned_u32 = u32::try_from(assigned_u64).map_err(|_| {
287 RouteDistinguisherParseError::AssignedNumberOutOfRange {
288 value: assigned_u64,
289 max: u64::from(u32::MAX),
290 }
291 })?;
292 let mut bytes = [0u8; 8];
293 bytes[0..2].copy_from_slice(&0u16.to_be_bytes());
294 bytes[2..4].copy_from_slice(&asn_u16.to_be_bytes());
295 bytes[4..8].copy_from_slice(&assigned_u32.to_be_bytes());
296 Ok(Self(bytes))
297 } else {
298 let assigned_u16 = u16::try_from(assigned_u64).map_err(|_| {
300 RouteDistinguisherParseError::AssignedNumberOutOfRange {
301 value: assigned_u64,
302 max: u64::from(u16::MAX),
303 }
304 })?;
305 let mut bytes = [0u8; 8];
306 bytes[0..2].copy_from_slice(&2u16.to_be_bytes());
307 bytes[2..6].copy_from_slice(&asn.to_be_bytes());
308 bytes[6..8].copy_from_slice(&assigned_u16.to_be_bytes());
309 Ok(Self(bytes))
310 }
311 }
312}
313
314fn parse_unsigned(s: &str) -> Result<u64, RouteDistinguisherParseError> {
315 s.parse::<u64>()
316 .map_err(|_| RouteDistinguisherParseError::InvalidAssignedNumber(s.to_string()))
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
325pub struct MplsLabel(pub u32);
326
327impl MplsLabel {
328 #[must_use]
330 pub const fn new(value: u32) -> Self {
331 Self(value & 0x00FF_FFFF)
332 }
333
334 #[must_use]
336 pub const fn value(&self) -> u32 {
337 self.0
338 }
339
340 #[must_use]
346 pub const fn as_vni(&self) -> u32 {
347 self.0
348 }
349
350 #[must_use]
352 pub const fn as_mpls_label(&self) -> u32 {
353 self.0 >> 4
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
359pub enum EvpnIpPrefixValue {
360 V4(Ipv4Prefix),
362 V6(Ipv6Prefix),
364}
365
366impl fmt::Display for EvpnIpPrefixValue {
367 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368 match self {
369 Self::V4(p) => write!(f, "{}/{}", p.addr, p.len),
370 Self::V6(p) => write!(f, "{}/{}", p.addr, p.len),
371 }
372 }
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
383pub struct EvpnEadPerEs {
384 pub rd: RouteDistinguisher,
386 pub esi: EthernetSegmentIdentifier,
388 pub ethernet_tag: EthernetTagId,
390 pub label: MplsLabel,
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
399pub struct EvpnEadPerEvi {
400 pub rd: RouteDistinguisher,
402 pub esi: EthernetSegmentIdentifier,
404 pub ethernet_tag: EthernetTagId,
406 pub label: MplsLabel,
408}
409
410#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
412pub struct EvpnMacIp {
413 pub rd: RouteDistinguisher,
415 pub esi: EthernetSegmentIdentifier,
417 pub ethernet_tag: EthernetTagId,
419 pub mac: MacAddress,
421 pub ip: Option<IpAddr>,
423 pub label1: MplsLabel,
425 pub label2: Option<MplsLabel>,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
431pub struct EvpnImet {
432 pub rd: RouteDistinguisher,
434 pub ethernet_tag: EthernetTagId,
436 pub originator_ip: IpAddr,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
442pub struct EvpnEs {
443 pub rd: RouteDistinguisher,
445 pub esi: EthernetSegmentIdentifier,
447 pub originator_ip: IpAddr,
449}
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
453pub struct EvpnIpPrefixRoute {
454 pub rd: RouteDistinguisher,
456 pub esi: EthernetSegmentIdentifier,
458 pub ethernet_tag: EthernetTagId,
460 pub prefix: EvpnIpPrefixValue,
462 pub gateway: IpAddr,
464 pub label: MplsLabel,
466}
467
468#[derive(Debug, Clone, PartialEq, Eq, Hash)]
478pub enum EvpnRoute {
479 EadPerEs(EvpnEadPerEs),
481 EadPerEvi(EvpnEadPerEvi),
483 MacIp(EvpnMacIp),
485 Imet(EvpnImet),
487 Es(EvpnEs),
489 IpPrefix(EvpnIpPrefixRoute),
491}
492
493impl EvpnRoute {
494 #[must_use]
496 pub const fn route_type(&self) -> u8 {
497 match self {
498 Self::EadPerEs(_) | Self::EadPerEvi(_) => 1,
499 Self::MacIp(_) => 2,
500 Self::Imet(_) => 3,
501 Self::Es(_) => 4,
502 Self::IpPrefix(_) => 5,
503 }
504 }
505
506 #[must_use]
508 pub fn key(&self) -> EvpnRouteKey {
509 match self {
510 Self::EadPerEs(r) => EvpnRouteKey::EadPerEs {
511 rd: r.rd,
512 esi: r.esi,
513 ethernet_tag: r.ethernet_tag,
514 },
515 Self::EadPerEvi(r) => EvpnRouteKey::EadPerEvi {
516 rd: r.rd,
517 esi: r.esi,
518 ethernet_tag: r.ethernet_tag,
519 },
520 Self::MacIp(r) => EvpnRouteKey::MacIp {
521 rd: r.rd,
522 ethernet_tag: r.ethernet_tag,
523 mac: r.mac,
524 ip: r.ip,
525 },
526 Self::Imet(r) => EvpnRouteKey::Imet {
527 rd: r.rd,
528 ethernet_tag: r.ethernet_tag,
529 originator_ip: r.originator_ip,
530 },
531 Self::Es(r) => EvpnRouteKey::Es {
532 rd: r.rd,
533 esi: r.esi,
534 originator_ip: r.originator_ip,
535 },
536 Self::IpPrefix(r) => EvpnRouteKey::IpPrefix {
537 rd: r.rd,
538 ethernet_tag: r.ethernet_tag,
539 prefix: r.prefix,
540 },
541 }
542 }
543}
544
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
551pub enum EvpnRouteKey {
552 EadPerEs {
554 rd: RouteDistinguisher,
556 esi: EthernetSegmentIdentifier,
558 ethernet_tag: EthernetTagId,
560 },
561 EadPerEvi {
563 rd: RouteDistinguisher,
565 esi: EthernetSegmentIdentifier,
567 ethernet_tag: EthernetTagId,
569 },
570 MacIp {
572 rd: RouteDistinguisher,
574 ethernet_tag: EthernetTagId,
576 mac: MacAddress,
578 ip: Option<IpAddr>,
580 },
581 Imet {
583 rd: RouteDistinguisher,
585 ethernet_tag: EthernetTagId,
587 originator_ip: IpAddr,
589 },
590 Es {
592 rd: RouteDistinguisher,
594 esi: EthernetSegmentIdentifier,
596 originator_ip: IpAddr,
598 },
599 IpPrefix {
601 rd: RouteDistinguisher,
603 ethernet_tag: EthernetTagId,
605 prefix: EvpnIpPrefixValue,
607 },
608}
609
610impl EvpnRouteKey {
611 #[must_use]
613 pub const fn route_type(&self) -> u8 {
614 match self {
615 Self::EadPerEs { .. } | Self::EadPerEvi { .. } => 1,
616 Self::MacIp { .. } => 2,
617 Self::Imet { .. } => 3,
618 Self::Es { .. } => 4,
619 Self::IpPrefix { .. } => 5,
620 }
621 }
622}
623
624fn decode_rd(buf: &[u8]) -> Result<RouteDistinguisher, DecodeError> {
629 if buf.len() < 8 {
630 return Err(DecodeError::MalformedField {
631 message_type: "UPDATE",
632 detail: "EVPN NLRI truncated: expected 8-byte Route Distinguisher".to_string(),
633 });
634 }
635 let mut bytes = [0u8; 8];
636 bytes.copy_from_slice(&buf[..8]);
637 Ok(RouteDistinguisher(bytes))
638}
639
640fn decode_esi(buf: &[u8]) -> Result<EthernetSegmentIdentifier, DecodeError> {
641 if buf.len() < 10 {
642 return Err(DecodeError::MalformedField {
643 message_type: "UPDATE",
644 detail: "EVPN NLRI truncated: expected 10-byte ESI".to_string(),
645 });
646 }
647 let mut bytes = [0u8; 10];
648 bytes.copy_from_slice(&buf[..10]);
649 Ok(EthernetSegmentIdentifier(bytes))
650}
651
652fn decode_ethernet_tag(buf: &[u8]) -> Result<EthernetTagId, DecodeError> {
653 if buf.len() < 4 {
654 return Err(DecodeError::MalformedField {
655 message_type: "UPDATE",
656 detail: "EVPN NLRI truncated: expected 4-byte Ethernet Tag".to_string(),
657 });
658 }
659 Ok(EthernetTagId(u32::from_be_bytes([
660 buf[0], buf[1], buf[2], buf[3],
661 ])))
662}
663
664fn decode_mpls_label(buf: &[u8]) -> Result<MplsLabel, DecodeError> {
665 if buf.len() < 3 {
666 return Err(DecodeError::MalformedField {
667 message_type: "UPDATE",
668 detail: "EVPN NLRI truncated: expected 3-byte MPLS label".to_string(),
669 });
670 }
671 let value = (u32::from(buf[0]) << 16) | (u32::from(buf[1]) << 8) | u32::from(buf[2]);
673 Ok(MplsLabel(value))
674}
675
676fn decode_ip_addr(buf: &[u8], len: usize, field: &str) -> Result<IpAddr, DecodeError> {
677 match len {
678 4 => {
679 if buf.len() < 4 {
680 return Err(DecodeError::MalformedField {
681 message_type: "UPDATE",
682 detail: format!("EVPN NLRI truncated: expected 4 bytes for {field}"),
683 });
684 }
685 Ok(IpAddr::V4(Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3])))
686 }
687 16 => {
688 if buf.len() < 16 {
689 return Err(DecodeError::MalformedField {
690 message_type: "UPDATE",
691 detail: format!("EVPN NLRI truncated: expected 16 bytes for {field}"),
692 });
693 }
694 let mut octets = [0u8; 16];
695 octets.copy_from_slice(&buf[..16]);
696 Ok(IpAddr::V6(Ipv6Addr::from(octets)))
697 }
698 other => Err(DecodeError::MalformedField {
699 message_type: "UPDATE",
700 detail: format!("EVPN NLRI {field} length {other} (expected 4 or 16)"),
701 }),
702 }
703}
704
705fn decode_type1(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
710 if payload.len() != 25 {
712 return Err(DecodeError::MalformedField {
713 message_type: "UPDATE",
714 detail: format!("EVPN Type 1 payload length {} (expected 25)", payload.len()),
715 });
716 }
717 let rd = decode_rd(&payload[0..8])?;
718 let esi = decode_esi(&payload[8..18])?;
719 if esi.is_zero() {
722 return Err(DecodeError::MalformedField {
723 message_type: "UPDATE",
724 detail: "EVPN Type 1 EAD route with all-zero ESI (RFC 7432 §7.1)".into(),
725 });
726 }
727 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
728 let label = decode_mpls_label(&payload[22..25])?;
729 if ethernet_tag.is_max_et() {
730 Ok(EvpnRoute::EadPerEs(EvpnEadPerEs {
731 rd,
732 esi,
733 ethernet_tag,
734 label,
735 }))
736 } else {
737 Ok(EvpnRoute::EadPerEvi(EvpnEadPerEvi {
738 rd,
739 esi,
740 ethernet_tag,
741 label,
742 }))
743 }
744}
745
746fn decode_type2(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
747 if payload.len() < 25 {
750 return Err(DecodeError::MalformedField {
751 message_type: "UPDATE",
752 detail: format!(
753 "EVPN Type 2 payload too short: {} bytes (need at least 25)",
754 payload.len()
755 ),
756 });
757 }
758 let rd = decode_rd(&payload[0..8])?;
759 let esi = decode_esi(&payload[8..18])?;
760 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
761 let mac_addr_len = payload[22];
762 if mac_addr_len != 48 {
763 return Err(DecodeError::MalformedField {
764 message_type: "UPDATE",
765 detail: format!("EVPN Type 2 MAC Addr Length {mac_addr_len} (expected 48)"),
766 });
767 }
768 if payload.len() < 23 + 6 + 1 {
769 return Err(DecodeError::MalformedField {
770 message_type: "UPDATE",
771 detail: "EVPN Type 2 truncated before IP Addr Length byte".into(),
772 });
773 }
774 let mac = MacAddress([
775 payload[23],
776 payload[24],
777 payload[25],
778 payload[26],
779 payload[27],
780 payload[28],
781 ]);
782 let ip_addr_len_bits = payload[29];
783 let ip_bytes_expected = match ip_addr_len_bits {
784 0 => 0,
785 32 => 4,
786 128 => 16,
787 other => {
788 return Err(DecodeError::MalformedField {
789 message_type: "UPDATE",
790 detail: format!("EVPN Type 2 IP Addr Length {other} bits (expected 0, 32, 128)"),
791 });
792 }
793 };
794 let ip_start = 30;
795 if payload.len() < ip_start + ip_bytes_expected + 3 {
796 return Err(DecodeError::MalformedField {
797 message_type: "UPDATE",
798 detail: format!(
799 "EVPN Type 2 truncated: need {} bytes for IP + Label1, have {}",
800 ip_bytes_expected + 3,
801 payload.len() - ip_start
802 ),
803 });
804 }
805 let ip = if ip_bytes_expected == 0 {
806 None
807 } else {
808 Some(decode_ip_addr(
809 &payload[ip_start..ip_start + ip_bytes_expected],
810 ip_bytes_expected,
811 "Type 2 IP",
812 )?)
813 };
814 let label1_start = ip_start + ip_bytes_expected;
815 let label1 = decode_mpls_label(&payload[label1_start..label1_start + 3])?;
816 let label2_start = label1_start + 3;
817 let label2 = match payload.len() - label2_start {
818 0 => None,
819 3 => Some(decode_mpls_label(&payload[label2_start..label2_start + 3])?),
820 other => {
821 return Err(DecodeError::MalformedField {
822 message_type: "UPDATE",
823 detail: format!(
824 "EVPN Type 2 trailing bytes {other} (expected 0 or 3 for optional Label2)"
825 ),
826 });
827 }
828 };
829 Ok(EvpnRoute::MacIp(EvpnMacIp {
830 rd,
831 esi,
832 ethernet_tag,
833 mac,
834 ip,
835 label1,
836 label2,
837 }))
838}
839
840fn decode_type3(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
841 if payload.len() < 13 {
843 return Err(DecodeError::MalformedField {
844 message_type: "UPDATE",
845 detail: format!(
846 "EVPN Type 3 payload too short: {} bytes (need at least 13)",
847 payload.len()
848 ),
849 });
850 }
851 let rd = decode_rd(&payload[0..8])?;
852 let ethernet_tag = decode_ethernet_tag(&payload[8..12])?;
853 let ip_len_bits = payload[12];
854 let ip_bytes = match ip_len_bits {
855 32 => 4,
856 128 => 16,
857 other => {
858 return Err(DecodeError::MalformedField {
859 message_type: "UPDATE",
860 detail: format!("EVPN Type 3 IP Addr Length {other} bits (expected 32 or 128)"),
861 });
862 }
863 };
864 if payload.len() != 13 + ip_bytes {
865 return Err(DecodeError::MalformedField {
866 message_type: "UPDATE",
867 detail: format!(
868 "EVPN Type 3 payload length {} (expected {})",
869 payload.len(),
870 13 + ip_bytes
871 ),
872 });
873 }
874 let originator_ip = decode_ip_addr(&payload[13..], ip_bytes, "Type 3 originator IP")?;
875 Ok(EvpnRoute::Imet(EvpnImet {
876 rd,
877 ethernet_tag,
878 originator_ip,
879 }))
880}
881
882fn decode_type4(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
883 if payload.len() < 19 {
885 return Err(DecodeError::MalformedField {
886 message_type: "UPDATE",
887 detail: format!(
888 "EVPN Type 4 payload too short: {} bytes (need at least 19)",
889 payload.len()
890 ),
891 });
892 }
893 let rd = decode_rd(&payload[0..8])?;
894 let esi = decode_esi(&payload[8..18])?;
895 if esi.is_zero() {
897 return Err(DecodeError::MalformedField {
898 message_type: "UPDATE",
899 detail: "EVPN Type 4 ES route with all-zero ESI (RFC 7432 §7.4)".into(),
900 });
901 }
902 let ip_len_bits = payload[18];
903 let ip_bytes = match ip_len_bits {
904 32 => 4,
905 128 => 16,
906 other => {
907 return Err(DecodeError::MalformedField {
908 message_type: "UPDATE",
909 detail: format!("EVPN Type 4 IP Addr Length {other} bits (expected 32 or 128)"),
910 });
911 }
912 };
913 if payload.len() != 19 + ip_bytes {
914 return Err(DecodeError::MalformedField {
915 message_type: "UPDATE",
916 detail: format!(
917 "EVPN Type 4 payload length {} (expected {})",
918 payload.len(),
919 19 + ip_bytes
920 ),
921 });
922 }
923 let originator_ip = decode_ip_addr(&payload[19..], ip_bytes, "Type 4 originator IP")?;
924 Ok(EvpnRoute::Es(EvpnEs {
925 rd,
926 esi,
927 originator_ip,
928 }))
929}
930
931fn decode_type5(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
932 let total = payload.len();
944 if total != 34 && total != 58 {
945 return Err(DecodeError::MalformedField {
946 message_type: "UPDATE",
947 detail: format!("EVPN Type 5 payload length {total} (expected 34 or 58)"),
948 });
949 }
950 let rd = decode_rd(&payload[0..8])?;
951 let esi = decode_esi(&payload[8..18])?;
952 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
953 let prefix_len = payload[22];
954 let is_v6 = total == 58;
955 let prefix = if is_v6 {
956 if prefix_len > 128 {
957 return Err(DecodeError::MalformedField {
958 message_type: "UPDATE",
959 detail: format!("EVPN Type 5 IPv6 prefix length {prefix_len} > 128"),
960 });
961 }
962 let mut octets = [0u8; 16];
963 octets.copy_from_slice(&payload[23..39]);
964 EvpnIpPrefixValue::V6(Ipv6Prefix::new(Ipv6Addr::from(octets), prefix_len))
965 } else {
966 if prefix_len > 32 {
967 return Err(DecodeError::MalformedField {
968 message_type: "UPDATE",
969 detail: format!("EVPN Type 5 IPv4 prefix length {prefix_len} > 32"),
970 });
971 }
972 let addr = Ipv4Addr::new(payload[23], payload[24], payload[25], payload[26]);
973 EvpnIpPrefixValue::V4(Ipv4Prefix::new(addr, prefix_len))
974 };
975 let (gateway, label_start) = if is_v6 {
976 let mut octets = [0u8; 16];
977 octets.copy_from_slice(&payload[39..55]);
978 (IpAddr::V6(Ipv6Addr::from(octets)), 55)
979 } else {
980 (
981 IpAddr::V4(Ipv4Addr::new(
982 payload[27],
983 payload[28],
984 payload[29],
985 payload[30],
986 )),
987 31,
988 )
989 };
990 let label = decode_mpls_label(&payload[label_start..label_start + 3])?;
991 Ok(EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
992 rd,
993 esi,
994 ethernet_tag,
995 prefix,
996 gateway,
997 label,
998 }))
999}
1000
1001pub fn decode_evpn_nlri(mut buf: &[u8]) -> Result<Vec<EvpnRoute>, DecodeError> {
1019 let mut routes = Vec::new();
1020 while !buf.is_empty() {
1021 if buf.len() < 2 {
1022 return Err(DecodeError::MalformedField {
1023 message_type: "UPDATE",
1024 detail: "EVPN NLRI truncated: need route-type + length bytes".into(),
1025 });
1026 }
1027 let route_type = buf[0];
1028 let length = usize::from(buf[1]);
1029 if buf.len() < 2 + length {
1030 return Err(DecodeError::MalformedField {
1031 message_type: "UPDATE",
1032 detail: format!(
1033 "EVPN NLRI truncated: route type {route_type} claims length {length}, \
1034 but only {} bytes remain",
1035 buf.len() - 2
1036 ),
1037 });
1038 }
1039 let payload = &buf[2..2 + length];
1040 match route_type {
1041 1 => routes.push(decode_type1(payload)?),
1042 2 => routes.push(decode_type2(payload)?),
1043 3 => routes.push(decode_type3(payload)?),
1044 4 => routes.push(decode_type4(payload)?),
1045 5 => routes.push(decode_type5(payload)?),
1046 _ => {}
1049 }
1050 buf = &buf[2 + length..];
1051 }
1052 Ok(routes)
1053}
1054
1055fn encode_mpls_label(label: MplsLabel, out: &mut Vec<u8>) {
1060 let v = label.0 & 0x00FF_FFFF;
1061 #[expect(clippy::cast_possible_truncation)]
1062 {
1063 out.push((v >> 16) as u8);
1064 out.push((v >> 8) as u8);
1065 out.push(v as u8);
1066 }
1067}
1068
1069fn encode_ip_addr(ip: IpAddr, out: &mut Vec<u8>) {
1070 match ip {
1071 IpAddr::V4(v4) => out.extend_from_slice(&v4.octets()),
1072 IpAddr::V6(v6) => out.extend_from_slice(&v6.octets()),
1073 }
1074}
1075
1076fn encode_type1_body(
1077 rd: RouteDistinguisher,
1078 esi: EthernetSegmentIdentifier,
1079 ethernet_tag: EthernetTagId,
1080 label: MplsLabel,
1081 out: &mut Vec<u8>,
1082) {
1083 out.extend_from_slice(&rd.0);
1084 out.extend_from_slice(&esi.0);
1085 out.extend_from_slice(ðernet_tag.0.to_be_bytes());
1086 encode_mpls_label(label, out);
1087}
1088
1089fn encode_type2_body(r: &EvpnMacIp, out: &mut Vec<u8>) {
1090 out.extend_from_slice(&r.rd.0);
1091 out.extend_from_slice(&r.esi.0);
1092 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
1093 out.push(48); out.extend_from_slice(&r.mac.0);
1095 match r.ip {
1096 None => out.push(0),
1097 Some(IpAddr::V4(v4)) => {
1098 out.push(32);
1099 out.extend_from_slice(&v4.octets());
1100 }
1101 Some(IpAddr::V6(v6)) => {
1102 out.push(128);
1103 out.extend_from_slice(&v6.octets());
1104 }
1105 }
1106 encode_mpls_label(r.label1, out);
1107 if let Some(label2) = r.label2 {
1108 encode_mpls_label(label2, out);
1109 }
1110}
1111
1112fn encode_type3_body(r: &EvpnImet, out: &mut Vec<u8>) {
1113 out.extend_from_slice(&r.rd.0);
1114 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
1115 match r.originator_ip {
1116 IpAddr::V4(_) => out.push(32),
1117 IpAddr::V6(_) => out.push(128),
1118 }
1119 encode_ip_addr(r.originator_ip, out);
1120}
1121
1122fn encode_type4_body(r: &EvpnEs, out: &mut Vec<u8>) {
1123 out.extend_from_slice(&r.rd.0);
1124 out.extend_from_slice(&r.esi.0);
1125 match r.originator_ip {
1126 IpAddr::V4(_) => out.push(32),
1127 IpAddr::V6(_) => out.push(128),
1128 }
1129 encode_ip_addr(r.originator_ip, out);
1130}
1131
1132fn encode_type5_body(r: &EvpnIpPrefixRoute, out: &mut Vec<u8>) {
1133 debug_assert!(
1139 matches!(
1140 (&r.prefix, &r.gateway),
1141 (EvpnIpPrefixValue::V4(_), IpAddr::V4(_)) | (EvpnIpPrefixValue::V6(_), IpAddr::V6(_))
1142 ),
1143 "EVPN Type 5: gateway family must match prefix family"
1144 );
1145 out.extend_from_slice(&r.rd.0);
1146 out.extend_from_slice(&r.esi.0);
1147 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
1148 match r.prefix {
1149 EvpnIpPrefixValue::V4(p) => {
1150 out.push(p.len);
1151 out.extend_from_slice(&p.addr.octets());
1152 if let IpAddr::V4(gw) = r.gateway {
1153 out.extend_from_slice(&gw.octets());
1154 } else {
1155 out.extend_from_slice(&Ipv4Addr::UNSPECIFIED.octets());
1156 }
1157 }
1158 EvpnIpPrefixValue::V6(p) => {
1159 out.push(p.len);
1160 out.extend_from_slice(&p.addr.octets());
1161 if let IpAddr::V6(gw) = r.gateway {
1162 out.extend_from_slice(&gw.octets());
1163 } else {
1164 out.extend_from_slice(&Ipv6Addr::UNSPECIFIED.octets());
1165 }
1166 }
1167 }
1168 encode_mpls_label(r.label, out);
1169}
1170
1171pub fn encode_evpn_nlri(routes: &[EvpnRoute], buf: &mut Vec<u8>) {
1173 for route in routes {
1174 let route_type = route.route_type();
1175 let len_placeholder = buf.len();
1176 buf.push(route_type);
1177 buf.push(0); let body_start = buf.len();
1179 match route {
1180 EvpnRoute::EadPerEs(r) => {
1181 debug_assert!(
1187 r.ethernet_tag.is_max_et(),
1188 "EVPN EAD-per-ES must carry MAX_ET ethernet tag"
1189 );
1190 encode_type1_body(r.rd, r.esi, EthernetTagId::MAX_ET, r.label, buf);
1191 }
1192 EvpnRoute::EadPerEvi(r) => {
1193 debug_assert!(
1194 !r.ethernet_tag.is_max_et(),
1195 "EVPN EAD-per-EVI must not carry MAX_ET ethernet tag"
1196 );
1197 encode_type1_body(r.rd, r.esi, r.ethernet_tag, r.label, buf);
1198 }
1199 EvpnRoute::MacIp(r) => encode_type2_body(r, buf),
1200 EvpnRoute::Imet(r) => encode_type3_body(r, buf),
1201 EvpnRoute::Es(r) => encode_type4_body(r, buf),
1202 EvpnRoute::IpPrefix(r) => encode_type5_body(r, buf),
1203 }
1204 let body_len = buf.len() - body_start;
1205 debug_assert!(
1206 u8::try_from(body_len).is_ok(),
1207 "EVPN NLRI body exceeds 255 bytes"
1208 );
1209 #[expect(clippy::cast_possible_truncation)]
1210 {
1211 buf[len_placeholder + 1] = body_len as u8;
1212 }
1213 }
1214}
1215
1216#[cfg(test)]
1221mod tests {
1222 use super::*;
1223
1224 fn sample_rd() -> RouteDistinguisher {
1225 RouteDistinguisher([0x00, 0x00, 0xFD, 0xE8, 0x00, 0x00, 0x00, 0x64])
1227 }
1228
1229 fn sample_esi() -> EthernetSegmentIdentifier {
1230 EthernetSegmentIdentifier([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A])
1231 }
1232
1233 fn roundtrip(routes: &[EvpnRoute]) {
1234 let mut buf = Vec::new();
1235 encode_evpn_nlri(routes, &mut buf);
1236 let decoded = decode_evpn_nlri(&buf).expect("decode should succeed");
1237 assert_eq!(routes, decoded.as_slice(), "round-trip mismatch");
1238 }
1239
1240 #[test]
1241 fn rd_display_type0() {
1242 assert_eq!(sample_rd().to_string(), "65000:100");
1243 }
1244
1245 #[test]
1246 fn rd_display_type1() {
1247 let rd = RouteDistinguisher([0x00, 0x01, 10, 0, 0, 1, 0x00, 0x42]);
1248 assert_eq!(rd.to_string(), "10.0.0.1:66");
1249 }
1250
1251 #[test]
1252 fn rd_parse_type0_roundtrip() {
1253 let rd: RouteDistinguisher = "65000:100".parse().unwrap();
1255 assert_eq!(rd.rd_type(), 0);
1256 assert_eq!(rd.to_string(), "65000:100");
1257 let rd_max: RouteDistinguisher = "65000:4294967295".parse().unwrap();
1259 assert_eq!(rd_max.rd_type(), 0);
1260 }
1261
1262 #[test]
1263 fn rd_parse_type1_roundtrip() {
1264 let rd: RouteDistinguisher = "10.0.0.1:66".parse().unwrap();
1265 assert_eq!(rd.rd_type(), 1);
1266 assert_eq!(rd.to_string(), "10.0.0.1:66");
1267 }
1268
1269 #[test]
1270 fn rd_parse_type2_roundtrip() {
1271 let rd: RouteDistinguisher = "4200000000:200".parse().unwrap();
1273 assert_eq!(rd.rd_type(), 2);
1274 assert_eq!(rd.to_string(), "4200000000:200");
1275 }
1276
1277 #[test]
1278 fn rd_parse_rejects_missing_colon() {
1279 let err = "65000".parse::<RouteDistinguisher>().unwrap_err();
1280 assert!(matches!(err, RouteDistinguisherParseError::MissingColon));
1281 }
1282
1283 #[test]
1284 fn rd_parse_rejects_invalid_admin() {
1285 let err = "not-an-asn:1".parse::<RouteDistinguisher>().unwrap_err();
1286 assert!(matches!(
1287 err,
1288 RouteDistinguisherParseError::InvalidAdministrator(_)
1289 ));
1290 }
1291
1292 #[test]
1293 fn rd_parse_rejects_invalid_assigned() {
1294 let err = "65000:abc".parse::<RouteDistinguisher>().unwrap_err();
1295 assert!(matches!(
1296 err,
1297 RouteDistinguisherParseError::InvalidAssignedNumber(_)
1298 ));
1299 }
1300
1301 #[test]
1302 fn rd_parse_rejects_assigned_overflow_type1() {
1303 let err = "10.0.0.1:65536".parse::<RouteDistinguisher>().unwrap_err();
1305 assert!(matches!(
1306 err,
1307 RouteDistinguisherParseError::AssignedNumberOutOfRange { max: 0xFFFF, .. }
1308 ));
1309 }
1310
1311 #[test]
1312 fn rd_parse_rejects_assigned_overflow_type2() {
1313 let err = "4200000000:65536"
1315 .parse::<RouteDistinguisher>()
1316 .unwrap_err();
1317 assert!(matches!(
1318 err,
1319 RouteDistinguisherParseError::AssignedNumberOutOfRange { max: 0xFFFF, .. }
1320 ));
1321 }
1322
1323 #[test]
1324 fn ethernet_tag_max_et() {
1325 assert!(EthernetTagId::MAX_ET.is_max_et());
1326 assert_eq!(EthernetTagId::MAX_ET.to_string(), "MAX_ET");
1327 assert!(!EthernetTagId(100).is_max_et());
1328 }
1329
1330 #[test]
1331 fn mac_display() {
1332 let mac = MacAddress([0x00, 0x11, 0x22, 0xaa, 0xbb, 0xcc]);
1333 assert_eq!(mac.to_string(), "00:11:22:aa:bb:cc");
1334 }
1335
1336 #[test]
1337 fn mpls_label_vxlan_vni() {
1338 let label = MplsLabel::new(10_000);
1339 assert_eq!(label.as_vni(), 10_000);
1340 }
1341
1342 #[test]
1343 fn roundtrip_type1_per_es() {
1344 roundtrip(&[EvpnRoute::EadPerEs(EvpnEadPerEs {
1345 rd: sample_rd(),
1346 esi: sample_esi(),
1347 ethernet_tag: EthernetTagId::MAX_ET,
1348 label: MplsLabel::new(500),
1349 })]);
1350 }
1351
1352 #[test]
1353 fn roundtrip_type1_per_evi() {
1354 roundtrip(&[EvpnRoute::EadPerEvi(EvpnEadPerEvi {
1355 rd: sample_rd(),
1356 esi: sample_esi(),
1357 ethernet_tag: EthernetTagId(200),
1358 label: MplsLabel::new(10_001),
1359 })]);
1360 }
1361
1362 #[test]
1363 fn roundtrip_type2_mac_only() {
1364 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1365 rd: sample_rd(),
1366 esi: EthernetSegmentIdentifier::ZERO,
1367 ethernet_tag: EthernetTagId(100),
1368 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1369 ip: None,
1370 label1: MplsLabel::new(10_000),
1371 label2: None,
1372 })]);
1373 }
1374
1375 #[test]
1376 fn roundtrip_type2_mac_ipv4_two_labels() {
1377 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1378 rd: sample_rd(),
1379 esi: sample_esi(),
1380 ethernet_tag: EthernetTagId(100),
1381 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1382 ip: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10))),
1383 label1: MplsLabel::new(10_000),
1384 label2: Some(MplsLabel::new(20_000)),
1385 })]);
1386 }
1387
1388 #[test]
1389 fn roundtrip_type2_mac_ipv6() {
1390 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1391 rd: sample_rd(),
1392 esi: sample_esi(),
1393 ethernet_tag: EthernetTagId(100),
1394 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1395 ip: Some(IpAddr::V6("2001:db8::10".parse().unwrap())),
1396 label1: MplsLabel::new(10_000),
1397 label2: None,
1398 })]);
1399 }
1400
1401 #[test]
1402 fn roundtrip_type3_ipv4() {
1403 roundtrip(&[EvpnRoute::Imet(EvpnImet {
1404 rd: sample_rd(),
1405 ethernet_tag: EthernetTagId(100),
1406 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1407 })]);
1408 }
1409
1410 #[test]
1411 fn roundtrip_type3_ipv6() {
1412 roundtrip(&[EvpnRoute::Imet(EvpnImet {
1413 rd: sample_rd(),
1414 ethernet_tag: EthernetTagId(100),
1415 originator_ip: IpAddr::V6("2001:db8::1".parse().unwrap()),
1416 })]);
1417 }
1418
1419 #[test]
1420 fn roundtrip_type4_ipv4() {
1421 roundtrip(&[EvpnRoute::Es(EvpnEs {
1422 rd: sample_rd(),
1423 esi: sample_esi(),
1424 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1425 })]);
1426 }
1427
1428 #[test]
1429 fn roundtrip_type5_ipv4() {
1430 roundtrip(&[EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1431 rd: sample_rd(),
1432 esi: EthernetSegmentIdentifier::ZERO,
1433 ethernet_tag: EthernetTagId(0),
1434 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 100, 0, 0), 24)),
1435 gateway: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1436 label: MplsLabel::new(20_001),
1437 })]);
1438 }
1439
1440 #[test]
1441 fn roundtrip_type5_ipv6() {
1442 roundtrip(&[EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1443 rd: sample_rd(),
1444 esi: EthernetSegmentIdentifier::ZERO,
1445 ethernet_tag: EthernetTagId(0),
1446 prefix: EvpnIpPrefixValue::V6(Ipv6Prefix::new("2001:db8:100::".parse().unwrap(), 48)),
1447 gateway: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
1448 label: MplsLabel::new(20_001),
1449 })]);
1450 }
1451
1452 #[test]
1453 fn roundtrip_all_types_one_nlri() {
1454 roundtrip(&[
1455 EvpnRoute::EadPerEs(EvpnEadPerEs {
1456 rd: sample_rd(),
1457 esi: sample_esi(),
1458 ethernet_tag: EthernetTagId::MAX_ET,
1459 label: MplsLabel::new(500),
1460 }),
1461 EvpnRoute::Imet(EvpnImet {
1462 rd: sample_rd(),
1463 ethernet_tag: EthernetTagId(100),
1464 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1465 }),
1466 EvpnRoute::MacIp(EvpnMacIp {
1467 rd: sample_rd(),
1468 esi: EthernetSegmentIdentifier::ZERO,
1469 ethernet_tag: EthernetTagId(100),
1470 mac: MacAddress([0xaa; 6]),
1471 ip: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5))),
1472 label1: MplsLabel::new(10_000),
1473 label2: None,
1474 }),
1475 EvpnRoute::Es(EvpnEs {
1476 rd: sample_rd(),
1477 esi: sample_esi(),
1478 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1479 }),
1480 EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1481 rd: sample_rd(),
1482 esi: EthernetSegmentIdentifier::ZERO,
1483 ethernet_tag: EthernetTagId(0),
1484 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(192, 168, 0, 0), 24)),
1485 gateway: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1486 label: MplsLabel::new(20_001),
1487 }),
1488 ]);
1489 }
1490
1491 #[test]
1492 fn decode_truncated_nlri_fails() {
1493 let bytes = [
1495 2u8, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1496 ];
1497 assert!(decode_evpn_nlri(&bytes).is_err());
1498 }
1499
1500 #[test]
1505 fn decode_skips_unknown_route_type() {
1506 let imet = EvpnRoute::Imet(EvpnImet {
1508 rd: sample_rd(),
1509 ethernet_tag: EthernetTagId(100),
1510 originator_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
1511 });
1512 let imet2 = EvpnRoute::Imet(EvpnImet {
1513 rd: sample_rd(),
1514 ethernet_tag: EthernetTagId(200),
1515 originator_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2)),
1516 });
1517 let mut buf = Vec::new();
1518 encode_evpn_nlri(std::slice::from_ref(&imet), &mut buf);
1519 buf.extend_from_slice(&[99u8, 4, 0xAA, 0xBB, 0xCC, 0xDD]);
1521 encode_evpn_nlri(std::slice::from_ref(&imet2), &mut buf);
1522
1523 let decoded = decode_evpn_nlri(&buf).unwrap();
1524 assert_eq!(decoded.len(), 2, "unknown type should be skipped");
1525 assert!(matches!(decoded[0], EvpnRoute::Imet(_)));
1526 assert!(matches!(decoded[1], EvpnRoute::Imet(_)));
1527 }
1528
1529 #[test]
1531 fn decode_unknown_route_type_truncated_still_fails() {
1532 let bytes = [99u8, 10, 0, 0];
1534 assert!(decode_evpn_nlri(&bytes).is_err());
1535 }
1536
1537 #[test]
1539 fn decode_type1_rejects_zero_esi() {
1540 let mut bytes = vec![1u8, 25];
1541 bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&[0u8; 10]); bytes.extend_from_slice(&[0xFF; 4]); bytes.extend_from_slice(&[0, 0, 0]); let err = decode_evpn_nlri(&bytes).unwrap_err();
1546 let DecodeError::MalformedField { detail, .. } = err else {
1547 panic!("expected MalformedField");
1548 };
1549 assert!(detail.contains("Type 1"), "unexpected detail: {detail}");
1550 }
1551
1552 #[test]
1554 fn decode_type4_rejects_zero_esi() {
1555 let mut bytes = vec![4u8, 23];
1556 bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&[0u8; 10]); bytes.push(32); bytes.extend_from_slice(&[10, 0, 0, 1]); let err = decode_evpn_nlri(&bytes).unwrap_err();
1561 let DecodeError::MalformedField { detail, .. } = err else {
1562 panic!("expected MalformedField");
1563 };
1564 assert!(detail.contains("Type 4"), "unexpected detail: {detail}");
1565 }
1566
1567 #[test]
1568 fn empty_buffer_decodes_to_empty() {
1569 assert_eq!(decode_evpn_nlri(&[]).unwrap(), Vec::<EvpnRoute>::new());
1570 }
1571
1572 #[test]
1573 fn route_key_discriminates_ead_per_es_vs_per_evi() {
1574 let per_es = EvpnRoute::EadPerEs(EvpnEadPerEs {
1575 rd: sample_rd(),
1576 esi: sample_esi(),
1577 ethernet_tag: EthernetTagId::MAX_ET,
1578 label: MplsLabel::new(500),
1579 });
1580 let per_evi = EvpnRoute::EadPerEvi(EvpnEadPerEvi {
1581 rd: sample_rd(),
1582 esi: sample_esi(),
1583 ethernet_tag: EthernetTagId(200),
1584 label: MplsLabel::new(500),
1585 });
1586 assert_ne!(per_es.key(), per_evi.key());
1587 }
1588
1589 #[test]
1594 fn ead_per_es_encode_round_trips_to_per_es() {
1595 let r = EvpnRoute::EadPerEs(EvpnEadPerEs {
1596 rd: sample_rd(),
1597 esi: sample_esi(),
1598 ethernet_tag: EthernetTagId::MAX_ET,
1599 label: MplsLabel::new(7),
1600 });
1601 let mut buf = Vec::new();
1602 encode_evpn_nlri(std::slice::from_ref(&r), &mut buf);
1603 let decoded = decode_evpn_nlri(&buf).unwrap();
1604 assert_eq!(decoded.len(), 1);
1605 assert!(matches!(decoded[0], EvpnRoute::EadPerEs(_)));
1606 }
1607
1608 #[test]
1613 #[should_panic(expected = "gateway family must match prefix family")]
1614 fn type5_encode_panics_on_family_mismatch_in_debug() {
1615 let r = EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1616 rd: sample_rd(),
1617 esi: EthernetSegmentIdentifier::ZERO,
1618 ethernet_tag: EthernetTagId(0),
1619 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8)),
1620 gateway: IpAddr::V6(Ipv6Addr::LOCALHOST),
1621 label: MplsLabel::new(100),
1622 });
1623 let mut buf = Vec::new();
1624 encode_evpn_nlri(&[r], &mut buf);
1625 }
1626}