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, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
208pub struct MplsLabel(pub u32);
209
210impl MplsLabel {
211 #[must_use]
213 pub const fn new(value: u32) -> Self {
214 Self(value & 0x00FF_FFFF)
215 }
216
217 #[must_use]
219 pub const fn value(&self) -> u32 {
220 self.0
221 }
222
223 #[must_use]
229 pub const fn as_vni(&self) -> u32 {
230 self.0
231 }
232
233 #[must_use]
235 pub const fn as_mpls_label(&self) -> u32 {
236 self.0 >> 4
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
242pub enum EvpnIpPrefixValue {
243 V4(Ipv4Prefix),
245 V6(Ipv6Prefix),
247}
248
249impl fmt::Display for EvpnIpPrefixValue {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 match self {
252 Self::V4(p) => write!(f, "{}/{}", p.addr, p.len),
253 Self::V6(p) => write!(f, "{}/{}", p.addr, p.len),
254 }
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
266pub struct EvpnEadPerEs {
267 pub rd: RouteDistinguisher,
269 pub esi: EthernetSegmentIdentifier,
271 pub ethernet_tag: EthernetTagId,
273 pub label: MplsLabel,
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
282pub struct EvpnEadPerEvi {
283 pub rd: RouteDistinguisher,
285 pub esi: EthernetSegmentIdentifier,
287 pub ethernet_tag: EthernetTagId,
289 pub label: MplsLabel,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
295pub struct EvpnMacIp {
296 pub rd: RouteDistinguisher,
298 pub esi: EthernetSegmentIdentifier,
300 pub ethernet_tag: EthernetTagId,
302 pub mac: MacAddress,
304 pub ip: Option<IpAddr>,
306 pub label1: MplsLabel,
308 pub label2: Option<MplsLabel>,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
314pub struct EvpnImet {
315 pub rd: RouteDistinguisher,
317 pub ethernet_tag: EthernetTagId,
319 pub originator_ip: IpAddr,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
325pub struct EvpnEs {
326 pub rd: RouteDistinguisher,
328 pub esi: EthernetSegmentIdentifier,
330 pub originator_ip: IpAddr,
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
336pub struct EvpnIpPrefixRoute {
337 pub rd: RouteDistinguisher,
339 pub esi: EthernetSegmentIdentifier,
341 pub ethernet_tag: EthernetTagId,
343 pub prefix: EvpnIpPrefixValue,
345 pub gateway: IpAddr,
347 pub label: MplsLabel,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Hash)]
361pub enum EvpnRoute {
362 EadPerEs(EvpnEadPerEs),
364 EadPerEvi(EvpnEadPerEvi),
366 MacIp(EvpnMacIp),
368 Imet(EvpnImet),
370 Es(EvpnEs),
372 IpPrefix(EvpnIpPrefixRoute),
374}
375
376impl EvpnRoute {
377 #[must_use]
379 pub const fn route_type(&self) -> u8 {
380 match self {
381 Self::EadPerEs(_) | Self::EadPerEvi(_) => 1,
382 Self::MacIp(_) => 2,
383 Self::Imet(_) => 3,
384 Self::Es(_) => 4,
385 Self::IpPrefix(_) => 5,
386 }
387 }
388
389 #[must_use]
391 pub fn key(&self) -> EvpnRouteKey {
392 match self {
393 Self::EadPerEs(r) => EvpnRouteKey::EadPerEs {
394 rd: r.rd,
395 esi: r.esi,
396 ethernet_tag: r.ethernet_tag,
397 },
398 Self::EadPerEvi(r) => EvpnRouteKey::EadPerEvi {
399 rd: r.rd,
400 esi: r.esi,
401 ethernet_tag: r.ethernet_tag,
402 },
403 Self::MacIp(r) => EvpnRouteKey::MacIp {
404 rd: r.rd,
405 ethernet_tag: r.ethernet_tag,
406 mac: r.mac,
407 ip: r.ip,
408 },
409 Self::Imet(r) => EvpnRouteKey::Imet {
410 rd: r.rd,
411 ethernet_tag: r.ethernet_tag,
412 originator_ip: r.originator_ip,
413 },
414 Self::Es(r) => EvpnRouteKey::Es {
415 rd: r.rd,
416 esi: r.esi,
417 originator_ip: r.originator_ip,
418 },
419 Self::IpPrefix(r) => EvpnRouteKey::IpPrefix {
420 rd: r.rd,
421 ethernet_tag: r.ethernet_tag,
422 prefix: r.prefix,
423 },
424 }
425 }
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434pub enum EvpnRouteKey {
435 EadPerEs {
437 rd: RouteDistinguisher,
439 esi: EthernetSegmentIdentifier,
441 ethernet_tag: EthernetTagId,
443 },
444 EadPerEvi {
446 rd: RouteDistinguisher,
448 esi: EthernetSegmentIdentifier,
450 ethernet_tag: EthernetTagId,
452 },
453 MacIp {
455 rd: RouteDistinguisher,
457 ethernet_tag: EthernetTagId,
459 mac: MacAddress,
461 ip: Option<IpAddr>,
463 },
464 Imet {
466 rd: RouteDistinguisher,
468 ethernet_tag: EthernetTagId,
470 originator_ip: IpAddr,
472 },
473 Es {
475 rd: RouteDistinguisher,
477 esi: EthernetSegmentIdentifier,
479 originator_ip: IpAddr,
481 },
482 IpPrefix {
484 rd: RouteDistinguisher,
486 ethernet_tag: EthernetTagId,
488 prefix: EvpnIpPrefixValue,
490 },
491}
492
493impl EvpnRouteKey {
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
507fn decode_rd(buf: &[u8]) -> Result<RouteDistinguisher, DecodeError> {
512 if buf.len() < 8 {
513 return Err(DecodeError::MalformedField {
514 message_type: "UPDATE",
515 detail: "EVPN NLRI truncated: expected 8-byte Route Distinguisher".to_string(),
516 });
517 }
518 let mut bytes = [0u8; 8];
519 bytes.copy_from_slice(&buf[..8]);
520 Ok(RouteDistinguisher(bytes))
521}
522
523fn decode_esi(buf: &[u8]) -> Result<EthernetSegmentIdentifier, DecodeError> {
524 if buf.len() < 10 {
525 return Err(DecodeError::MalformedField {
526 message_type: "UPDATE",
527 detail: "EVPN NLRI truncated: expected 10-byte ESI".to_string(),
528 });
529 }
530 let mut bytes = [0u8; 10];
531 bytes.copy_from_slice(&buf[..10]);
532 Ok(EthernetSegmentIdentifier(bytes))
533}
534
535fn decode_ethernet_tag(buf: &[u8]) -> Result<EthernetTagId, DecodeError> {
536 if buf.len() < 4 {
537 return Err(DecodeError::MalformedField {
538 message_type: "UPDATE",
539 detail: "EVPN NLRI truncated: expected 4-byte Ethernet Tag".to_string(),
540 });
541 }
542 Ok(EthernetTagId(u32::from_be_bytes([
543 buf[0], buf[1], buf[2], buf[3],
544 ])))
545}
546
547fn decode_mpls_label(buf: &[u8]) -> Result<MplsLabel, DecodeError> {
548 if buf.len() < 3 {
549 return Err(DecodeError::MalformedField {
550 message_type: "UPDATE",
551 detail: "EVPN NLRI truncated: expected 3-byte MPLS label".to_string(),
552 });
553 }
554 let value = (u32::from(buf[0]) << 16) | (u32::from(buf[1]) << 8) | u32::from(buf[2]);
556 Ok(MplsLabel(value))
557}
558
559fn decode_ip_addr(buf: &[u8], len: usize, field: &str) -> Result<IpAddr, DecodeError> {
560 match len {
561 4 => {
562 if buf.len() < 4 {
563 return Err(DecodeError::MalformedField {
564 message_type: "UPDATE",
565 detail: format!("EVPN NLRI truncated: expected 4 bytes for {field}"),
566 });
567 }
568 Ok(IpAddr::V4(Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3])))
569 }
570 16 => {
571 if buf.len() < 16 {
572 return Err(DecodeError::MalformedField {
573 message_type: "UPDATE",
574 detail: format!("EVPN NLRI truncated: expected 16 bytes for {field}"),
575 });
576 }
577 let mut octets = [0u8; 16];
578 octets.copy_from_slice(&buf[..16]);
579 Ok(IpAddr::V6(Ipv6Addr::from(octets)))
580 }
581 other => Err(DecodeError::MalformedField {
582 message_type: "UPDATE",
583 detail: format!("EVPN NLRI {field} length {other} (expected 4 or 16)"),
584 }),
585 }
586}
587
588fn decode_type1(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
593 if payload.len() != 25 {
595 return Err(DecodeError::MalformedField {
596 message_type: "UPDATE",
597 detail: format!("EVPN Type 1 payload length {} (expected 25)", payload.len()),
598 });
599 }
600 let rd = decode_rd(&payload[0..8])?;
601 let esi = decode_esi(&payload[8..18])?;
602 if esi.is_zero() {
605 return Err(DecodeError::MalformedField {
606 message_type: "UPDATE",
607 detail: "EVPN Type 1 EAD route with all-zero ESI (RFC 7432 §7.1)".into(),
608 });
609 }
610 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
611 let label = decode_mpls_label(&payload[22..25])?;
612 if ethernet_tag.is_max_et() {
613 Ok(EvpnRoute::EadPerEs(EvpnEadPerEs {
614 rd,
615 esi,
616 ethernet_tag,
617 label,
618 }))
619 } else {
620 Ok(EvpnRoute::EadPerEvi(EvpnEadPerEvi {
621 rd,
622 esi,
623 ethernet_tag,
624 label,
625 }))
626 }
627}
628
629fn decode_type2(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
630 if payload.len() < 25 {
633 return Err(DecodeError::MalformedField {
634 message_type: "UPDATE",
635 detail: format!(
636 "EVPN Type 2 payload too short: {} bytes (need at least 25)",
637 payload.len()
638 ),
639 });
640 }
641 let rd = decode_rd(&payload[0..8])?;
642 let esi = decode_esi(&payload[8..18])?;
643 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
644 let mac_addr_len = payload[22];
645 if mac_addr_len != 48 {
646 return Err(DecodeError::MalformedField {
647 message_type: "UPDATE",
648 detail: format!("EVPN Type 2 MAC Addr Length {mac_addr_len} (expected 48)"),
649 });
650 }
651 if payload.len() < 23 + 6 + 1 {
652 return Err(DecodeError::MalformedField {
653 message_type: "UPDATE",
654 detail: "EVPN Type 2 truncated before IP Addr Length byte".into(),
655 });
656 }
657 let mac = MacAddress([
658 payload[23],
659 payload[24],
660 payload[25],
661 payload[26],
662 payload[27],
663 payload[28],
664 ]);
665 let ip_addr_len_bits = payload[29];
666 let ip_bytes_expected = match ip_addr_len_bits {
667 0 => 0,
668 32 => 4,
669 128 => 16,
670 other => {
671 return Err(DecodeError::MalformedField {
672 message_type: "UPDATE",
673 detail: format!("EVPN Type 2 IP Addr Length {other} bits (expected 0, 32, 128)"),
674 });
675 }
676 };
677 let ip_start = 30;
678 if payload.len() < ip_start + ip_bytes_expected + 3 {
679 return Err(DecodeError::MalformedField {
680 message_type: "UPDATE",
681 detail: format!(
682 "EVPN Type 2 truncated: need {} bytes for IP + Label1, have {}",
683 ip_bytes_expected + 3,
684 payload.len() - ip_start
685 ),
686 });
687 }
688 let ip = if ip_bytes_expected == 0 {
689 None
690 } else {
691 Some(decode_ip_addr(
692 &payload[ip_start..ip_start + ip_bytes_expected],
693 ip_bytes_expected,
694 "Type 2 IP",
695 )?)
696 };
697 let label1_start = ip_start + ip_bytes_expected;
698 let label1 = decode_mpls_label(&payload[label1_start..label1_start + 3])?;
699 let label2_start = label1_start + 3;
700 let label2 = match payload.len() - label2_start {
701 0 => None,
702 3 => Some(decode_mpls_label(&payload[label2_start..label2_start + 3])?),
703 other => {
704 return Err(DecodeError::MalformedField {
705 message_type: "UPDATE",
706 detail: format!(
707 "EVPN Type 2 trailing bytes {other} (expected 0 or 3 for optional Label2)"
708 ),
709 });
710 }
711 };
712 Ok(EvpnRoute::MacIp(EvpnMacIp {
713 rd,
714 esi,
715 ethernet_tag,
716 mac,
717 ip,
718 label1,
719 label2,
720 }))
721}
722
723fn decode_type3(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
724 if payload.len() < 13 {
726 return Err(DecodeError::MalformedField {
727 message_type: "UPDATE",
728 detail: format!(
729 "EVPN Type 3 payload too short: {} bytes (need at least 13)",
730 payload.len()
731 ),
732 });
733 }
734 let rd = decode_rd(&payload[0..8])?;
735 let ethernet_tag = decode_ethernet_tag(&payload[8..12])?;
736 let ip_len_bits = payload[12];
737 let ip_bytes = match ip_len_bits {
738 32 => 4,
739 128 => 16,
740 other => {
741 return Err(DecodeError::MalformedField {
742 message_type: "UPDATE",
743 detail: format!("EVPN Type 3 IP Addr Length {other} bits (expected 32 or 128)"),
744 });
745 }
746 };
747 if payload.len() != 13 + ip_bytes {
748 return Err(DecodeError::MalformedField {
749 message_type: "UPDATE",
750 detail: format!(
751 "EVPN Type 3 payload length {} (expected {})",
752 payload.len(),
753 13 + ip_bytes
754 ),
755 });
756 }
757 let originator_ip = decode_ip_addr(&payload[13..], ip_bytes, "Type 3 originator IP")?;
758 Ok(EvpnRoute::Imet(EvpnImet {
759 rd,
760 ethernet_tag,
761 originator_ip,
762 }))
763}
764
765fn decode_type4(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
766 if payload.len() < 19 {
768 return Err(DecodeError::MalformedField {
769 message_type: "UPDATE",
770 detail: format!(
771 "EVPN Type 4 payload too short: {} bytes (need at least 19)",
772 payload.len()
773 ),
774 });
775 }
776 let rd = decode_rd(&payload[0..8])?;
777 let esi = decode_esi(&payload[8..18])?;
778 if esi.is_zero() {
780 return Err(DecodeError::MalformedField {
781 message_type: "UPDATE",
782 detail: "EVPN Type 4 ES route with all-zero ESI (RFC 7432 §7.4)".into(),
783 });
784 }
785 let ip_len_bits = payload[18];
786 let ip_bytes = match ip_len_bits {
787 32 => 4,
788 128 => 16,
789 other => {
790 return Err(DecodeError::MalformedField {
791 message_type: "UPDATE",
792 detail: format!("EVPN Type 4 IP Addr Length {other} bits (expected 32 or 128)"),
793 });
794 }
795 };
796 if payload.len() != 19 + ip_bytes {
797 return Err(DecodeError::MalformedField {
798 message_type: "UPDATE",
799 detail: format!(
800 "EVPN Type 4 payload length {} (expected {})",
801 payload.len(),
802 19 + ip_bytes
803 ),
804 });
805 }
806 let originator_ip = decode_ip_addr(&payload[19..], ip_bytes, "Type 4 originator IP")?;
807 Ok(EvpnRoute::Es(EvpnEs {
808 rd,
809 esi,
810 originator_ip,
811 }))
812}
813
814fn decode_type5(payload: &[u8]) -> Result<EvpnRoute, DecodeError> {
815 let total = payload.len();
827 if total != 34 && total != 58 {
828 return Err(DecodeError::MalformedField {
829 message_type: "UPDATE",
830 detail: format!("EVPN Type 5 payload length {total} (expected 34 or 58)"),
831 });
832 }
833 let rd = decode_rd(&payload[0..8])?;
834 let esi = decode_esi(&payload[8..18])?;
835 let ethernet_tag = decode_ethernet_tag(&payload[18..22])?;
836 let prefix_len = payload[22];
837 let is_v6 = total == 58;
838 let prefix = if is_v6 {
839 if prefix_len > 128 {
840 return Err(DecodeError::MalformedField {
841 message_type: "UPDATE",
842 detail: format!("EVPN Type 5 IPv6 prefix length {prefix_len} > 128"),
843 });
844 }
845 let mut octets = [0u8; 16];
846 octets.copy_from_slice(&payload[23..39]);
847 EvpnIpPrefixValue::V6(Ipv6Prefix::new(Ipv6Addr::from(octets), prefix_len))
848 } else {
849 if prefix_len > 32 {
850 return Err(DecodeError::MalformedField {
851 message_type: "UPDATE",
852 detail: format!("EVPN Type 5 IPv4 prefix length {prefix_len} > 32"),
853 });
854 }
855 let addr = Ipv4Addr::new(payload[23], payload[24], payload[25], payload[26]);
856 EvpnIpPrefixValue::V4(Ipv4Prefix::new(addr, prefix_len))
857 };
858 let (gateway, label_start) = if is_v6 {
859 let mut octets = [0u8; 16];
860 octets.copy_from_slice(&payload[39..55]);
861 (IpAddr::V6(Ipv6Addr::from(octets)), 55)
862 } else {
863 (
864 IpAddr::V4(Ipv4Addr::new(
865 payload[27],
866 payload[28],
867 payload[29],
868 payload[30],
869 )),
870 31,
871 )
872 };
873 let label = decode_mpls_label(&payload[label_start..label_start + 3])?;
874 Ok(EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
875 rd,
876 esi,
877 ethernet_tag,
878 prefix,
879 gateway,
880 label,
881 }))
882}
883
884pub fn decode_evpn_nlri(mut buf: &[u8]) -> Result<Vec<EvpnRoute>, DecodeError> {
902 let mut routes = Vec::new();
903 while !buf.is_empty() {
904 if buf.len() < 2 {
905 return Err(DecodeError::MalformedField {
906 message_type: "UPDATE",
907 detail: "EVPN NLRI truncated: need route-type + length bytes".into(),
908 });
909 }
910 let route_type = buf[0];
911 let length = usize::from(buf[1]);
912 if buf.len() < 2 + length {
913 return Err(DecodeError::MalformedField {
914 message_type: "UPDATE",
915 detail: format!(
916 "EVPN NLRI truncated: route type {route_type} claims length {length}, \
917 but only {} bytes remain",
918 buf.len() - 2
919 ),
920 });
921 }
922 let payload = &buf[2..2 + length];
923 match route_type {
924 1 => routes.push(decode_type1(payload)?),
925 2 => routes.push(decode_type2(payload)?),
926 3 => routes.push(decode_type3(payload)?),
927 4 => routes.push(decode_type4(payload)?),
928 5 => routes.push(decode_type5(payload)?),
929 _ => {}
932 }
933 buf = &buf[2 + length..];
934 }
935 Ok(routes)
936}
937
938fn encode_mpls_label(label: MplsLabel, out: &mut Vec<u8>) {
943 let v = label.0 & 0x00FF_FFFF;
944 #[expect(clippy::cast_possible_truncation)]
945 {
946 out.push((v >> 16) as u8);
947 out.push((v >> 8) as u8);
948 out.push(v as u8);
949 }
950}
951
952fn encode_ip_addr(ip: IpAddr, out: &mut Vec<u8>) {
953 match ip {
954 IpAddr::V4(v4) => out.extend_from_slice(&v4.octets()),
955 IpAddr::V6(v6) => out.extend_from_slice(&v6.octets()),
956 }
957}
958
959fn encode_type1_body(
960 rd: RouteDistinguisher,
961 esi: EthernetSegmentIdentifier,
962 ethernet_tag: EthernetTagId,
963 label: MplsLabel,
964 out: &mut Vec<u8>,
965) {
966 out.extend_from_slice(&rd.0);
967 out.extend_from_slice(&esi.0);
968 out.extend_from_slice(ðernet_tag.0.to_be_bytes());
969 encode_mpls_label(label, out);
970}
971
972fn encode_type2_body(r: &EvpnMacIp, out: &mut Vec<u8>) {
973 out.extend_from_slice(&r.rd.0);
974 out.extend_from_slice(&r.esi.0);
975 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
976 out.push(48); out.extend_from_slice(&r.mac.0);
978 match r.ip {
979 None => out.push(0),
980 Some(IpAddr::V4(v4)) => {
981 out.push(32);
982 out.extend_from_slice(&v4.octets());
983 }
984 Some(IpAddr::V6(v6)) => {
985 out.push(128);
986 out.extend_from_slice(&v6.octets());
987 }
988 }
989 encode_mpls_label(r.label1, out);
990 if let Some(label2) = r.label2 {
991 encode_mpls_label(label2, out);
992 }
993}
994
995fn encode_type3_body(r: &EvpnImet, out: &mut Vec<u8>) {
996 out.extend_from_slice(&r.rd.0);
997 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
998 match r.originator_ip {
999 IpAddr::V4(_) => out.push(32),
1000 IpAddr::V6(_) => out.push(128),
1001 }
1002 encode_ip_addr(r.originator_ip, out);
1003}
1004
1005fn encode_type4_body(r: &EvpnEs, out: &mut Vec<u8>) {
1006 out.extend_from_slice(&r.rd.0);
1007 out.extend_from_slice(&r.esi.0);
1008 match r.originator_ip {
1009 IpAddr::V4(_) => out.push(32),
1010 IpAddr::V6(_) => out.push(128),
1011 }
1012 encode_ip_addr(r.originator_ip, out);
1013}
1014
1015fn encode_type5_body(r: &EvpnIpPrefixRoute, out: &mut Vec<u8>) {
1016 debug_assert!(
1022 matches!(
1023 (&r.prefix, &r.gateway),
1024 (EvpnIpPrefixValue::V4(_), IpAddr::V4(_)) | (EvpnIpPrefixValue::V6(_), IpAddr::V6(_))
1025 ),
1026 "EVPN Type 5: gateway family must match prefix family"
1027 );
1028 out.extend_from_slice(&r.rd.0);
1029 out.extend_from_slice(&r.esi.0);
1030 out.extend_from_slice(&r.ethernet_tag.0.to_be_bytes());
1031 match r.prefix {
1032 EvpnIpPrefixValue::V4(p) => {
1033 out.push(p.len);
1034 out.extend_from_slice(&p.addr.octets());
1035 if let IpAddr::V4(gw) = r.gateway {
1036 out.extend_from_slice(&gw.octets());
1037 } else {
1038 out.extend_from_slice(&Ipv4Addr::UNSPECIFIED.octets());
1039 }
1040 }
1041 EvpnIpPrefixValue::V6(p) => {
1042 out.push(p.len);
1043 out.extend_from_slice(&p.addr.octets());
1044 if let IpAddr::V6(gw) = r.gateway {
1045 out.extend_from_slice(&gw.octets());
1046 } else {
1047 out.extend_from_slice(&Ipv6Addr::UNSPECIFIED.octets());
1048 }
1049 }
1050 }
1051 encode_mpls_label(r.label, out);
1052}
1053
1054pub fn encode_evpn_nlri(routes: &[EvpnRoute], buf: &mut Vec<u8>) {
1056 for route in routes {
1057 let route_type = route.route_type();
1058 let len_placeholder = buf.len();
1059 buf.push(route_type);
1060 buf.push(0); let body_start = buf.len();
1062 match route {
1063 EvpnRoute::EadPerEs(r) => {
1064 debug_assert!(
1070 r.ethernet_tag.is_max_et(),
1071 "EVPN EAD-per-ES must carry MAX_ET ethernet tag"
1072 );
1073 encode_type1_body(r.rd, r.esi, EthernetTagId::MAX_ET, r.label, buf);
1074 }
1075 EvpnRoute::EadPerEvi(r) => {
1076 debug_assert!(
1077 !r.ethernet_tag.is_max_et(),
1078 "EVPN EAD-per-EVI must not carry MAX_ET ethernet tag"
1079 );
1080 encode_type1_body(r.rd, r.esi, r.ethernet_tag, r.label, buf);
1081 }
1082 EvpnRoute::MacIp(r) => encode_type2_body(r, buf),
1083 EvpnRoute::Imet(r) => encode_type3_body(r, buf),
1084 EvpnRoute::Es(r) => encode_type4_body(r, buf),
1085 EvpnRoute::IpPrefix(r) => encode_type5_body(r, buf),
1086 }
1087 let body_len = buf.len() - body_start;
1088 debug_assert!(
1089 u8::try_from(body_len).is_ok(),
1090 "EVPN NLRI body exceeds 255 bytes"
1091 );
1092 #[expect(clippy::cast_possible_truncation)]
1093 {
1094 buf[len_placeholder + 1] = body_len as u8;
1095 }
1096 }
1097}
1098
1099#[cfg(test)]
1104mod tests {
1105 use super::*;
1106
1107 fn sample_rd() -> RouteDistinguisher {
1108 RouteDistinguisher([0x00, 0x00, 0xFD, 0xE8, 0x00, 0x00, 0x00, 0x64])
1110 }
1111
1112 fn sample_esi() -> EthernetSegmentIdentifier {
1113 EthernetSegmentIdentifier([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A])
1114 }
1115
1116 fn roundtrip(routes: &[EvpnRoute]) {
1117 let mut buf = Vec::new();
1118 encode_evpn_nlri(routes, &mut buf);
1119 let decoded = decode_evpn_nlri(&buf).expect("decode should succeed");
1120 assert_eq!(routes, decoded.as_slice(), "round-trip mismatch");
1121 }
1122
1123 #[test]
1124 fn rd_display_type0() {
1125 assert_eq!(sample_rd().to_string(), "65000:100");
1126 }
1127
1128 #[test]
1129 fn rd_display_type1() {
1130 let rd = RouteDistinguisher([0x00, 0x01, 10, 0, 0, 1, 0x00, 0x42]);
1131 assert_eq!(rd.to_string(), "10.0.0.1:66");
1132 }
1133
1134 #[test]
1135 fn ethernet_tag_max_et() {
1136 assert!(EthernetTagId::MAX_ET.is_max_et());
1137 assert_eq!(EthernetTagId::MAX_ET.to_string(), "MAX_ET");
1138 assert!(!EthernetTagId(100).is_max_et());
1139 }
1140
1141 #[test]
1142 fn mac_display() {
1143 let mac = MacAddress([0x00, 0x11, 0x22, 0xaa, 0xbb, 0xcc]);
1144 assert_eq!(mac.to_string(), "00:11:22:aa:bb:cc");
1145 }
1146
1147 #[test]
1148 fn mpls_label_vxlan_vni() {
1149 let label = MplsLabel::new(10_000);
1150 assert_eq!(label.as_vni(), 10_000);
1151 }
1152
1153 #[test]
1154 fn roundtrip_type1_per_es() {
1155 roundtrip(&[EvpnRoute::EadPerEs(EvpnEadPerEs {
1156 rd: sample_rd(),
1157 esi: sample_esi(),
1158 ethernet_tag: EthernetTagId::MAX_ET,
1159 label: MplsLabel::new(500),
1160 })]);
1161 }
1162
1163 #[test]
1164 fn roundtrip_type1_per_evi() {
1165 roundtrip(&[EvpnRoute::EadPerEvi(EvpnEadPerEvi {
1166 rd: sample_rd(),
1167 esi: sample_esi(),
1168 ethernet_tag: EthernetTagId(200),
1169 label: MplsLabel::new(10_001),
1170 })]);
1171 }
1172
1173 #[test]
1174 fn roundtrip_type2_mac_only() {
1175 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1176 rd: sample_rd(),
1177 esi: EthernetSegmentIdentifier::ZERO,
1178 ethernet_tag: EthernetTagId(100),
1179 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1180 ip: None,
1181 label1: MplsLabel::new(10_000),
1182 label2: None,
1183 })]);
1184 }
1185
1186 #[test]
1187 fn roundtrip_type2_mac_ipv4_two_labels() {
1188 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1189 rd: sample_rd(),
1190 esi: sample_esi(),
1191 ethernet_tag: EthernetTagId(100),
1192 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1193 ip: Some(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 10))),
1194 label1: MplsLabel::new(10_000),
1195 label2: Some(MplsLabel::new(20_000)),
1196 })]);
1197 }
1198
1199 #[test]
1200 fn roundtrip_type2_mac_ipv6() {
1201 roundtrip(&[EvpnRoute::MacIp(EvpnMacIp {
1202 rd: sample_rd(),
1203 esi: sample_esi(),
1204 ethernet_tag: EthernetTagId(100),
1205 mac: MacAddress([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]),
1206 ip: Some(IpAddr::V6("2001:db8::10".parse().unwrap())),
1207 label1: MplsLabel::new(10_000),
1208 label2: None,
1209 })]);
1210 }
1211
1212 #[test]
1213 fn roundtrip_type3_ipv4() {
1214 roundtrip(&[EvpnRoute::Imet(EvpnImet {
1215 rd: sample_rd(),
1216 ethernet_tag: EthernetTagId(100),
1217 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1218 })]);
1219 }
1220
1221 #[test]
1222 fn roundtrip_type3_ipv6() {
1223 roundtrip(&[EvpnRoute::Imet(EvpnImet {
1224 rd: sample_rd(),
1225 ethernet_tag: EthernetTagId(100),
1226 originator_ip: IpAddr::V6("2001:db8::1".parse().unwrap()),
1227 })]);
1228 }
1229
1230 #[test]
1231 fn roundtrip_type4_ipv4() {
1232 roundtrip(&[EvpnRoute::Es(EvpnEs {
1233 rd: sample_rd(),
1234 esi: sample_esi(),
1235 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1236 })]);
1237 }
1238
1239 #[test]
1240 fn roundtrip_type5_ipv4() {
1241 roundtrip(&[EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1242 rd: sample_rd(),
1243 esi: EthernetSegmentIdentifier::ZERO,
1244 ethernet_tag: EthernetTagId(0),
1245 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 100, 0, 0), 24)),
1246 gateway: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1247 label: MplsLabel::new(20_001),
1248 })]);
1249 }
1250
1251 #[test]
1252 fn roundtrip_type5_ipv6() {
1253 roundtrip(&[EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1254 rd: sample_rd(),
1255 esi: EthernetSegmentIdentifier::ZERO,
1256 ethernet_tag: EthernetTagId(0),
1257 prefix: EvpnIpPrefixValue::V6(Ipv6Prefix::new("2001:db8:100::".parse().unwrap(), 48)),
1258 gateway: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
1259 label: MplsLabel::new(20_001),
1260 })]);
1261 }
1262
1263 #[test]
1264 fn roundtrip_all_types_one_nlri() {
1265 roundtrip(&[
1266 EvpnRoute::EadPerEs(EvpnEadPerEs {
1267 rd: sample_rd(),
1268 esi: sample_esi(),
1269 ethernet_tag: EthernetTagId::MAX_ET,
1270 label: MplsLabel::new(500),
1271 }),
1272 EvpnRoute::Imet(EvpnImet {
1273 rd: sample_rd(),
1274 ethernet_tag: EthernetTagId(100),
1275 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1276 }),
1277 EvpnRoute::MacIp(EvpnMacIp {
1278 rd: sample_rd(),
1279 esi: EthernetSegmentIdentifier::ZERO,
1280 ethernet_tag: EthernetTagId(100),
1281 mac: MacAddress([0xaa; 6]),
1282 ip: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5))),
1283 label1: MplsLabel::new(10_000),
1284 label2: None,
1285 }),
1286 EvpnRoute::Es(EvpnEs {
1287 rd: sample_rd(),
1288 esi: sample_esi(),
1289 originator_ip: IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1290 }),
1291 EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1292 rd: sample_rd(),
1293 esi: EthernetSegmentIdentifier::ZERO,
1294 ethernet_tag: EthernetTagId(0),
1295 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(192, 168, 0, 0), 24)),
1296 gateway: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
1297 label: MplsLabel::new(20_001),
1298 }),
1299 ]);
1300 }
1301
1302 #[test]
1303 fn decode_truncated_nlri_fails() {
1304 let bytes = [
1306 2u8, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1307 ];
1308 assert!(decode_evpn_nlri(&bytes).is_err());
1309 }
1310
1311 #[test]
1316 fn decode_skips_unknown_route_type() {
1317 let imet = EvpnRoute::Imet(EvpnImet {
1319 rd: sample_rd(),
1320 ethernet_tag: EthernetTagId(100),
1321 originator_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
1322 });
1323 let imet2 = EvpnRoute::Imet(EvpnImet {
1324 rd: sample_rd(),
1325 ethernet_tag: EthernetTagId(200),
1326 originator_ip: IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2)),
1327 });
1328 let mut buf = Vec::new();
1329 encode_evpn_nlri(std::slice::from_ref(&imet), &mut buf);
1330 buf.extend_from_slice(&[99u8, 4, 0xAA, 0xBB, 0xCC, 0xDD]);
1332 encode_evpn_nlri(std::slice::from_ref(&imet2), &mut buf);
1333
1334 let decoded = decode_evpn_nlri(&buf).unwrap();
1335 assert_eq!(decoded.len(), 2, "unknown type should be skipped");
1336 assert!(matches!(decoded[0], EvpnRoute::Imet(_)));
1337 assert!(matches!(decoded[1], EvpnRoute::Imet(_)));
1338 }
1339
1340 #[test]
1342 fn decode_unknown_route_type_truncated_still_fails() {
1343 let bytes = [99u8, 10, 0, 0];
1345 assert!(decode_evpn_nlri(&bytes).is_err());
1346 }
1347
1348 #[test]
1350 fn decode_type1_rejects_zero_esi() {
1351 let mut bytes = vec![1u8, 25];
1352 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();
1357 let DecodeError::MalformedField { detail, .. } = err else {
1358 panic!("expected MalformedField");
1359 };
1360 assert!(detail.contains("Type 1"), "unexpected detail: {detail}");
1361 }
1362
1363 #[test]
1365 fn decode_type4_rejects_zero_esi() {
1366 let mut bytes = vec![4u8, 23];
1367 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();
1372 let DecodeError::MalformedField { detail, .. } = err else {
1373 panic!("expected MalformedField");
1374 };
1375 assert!(detail.contains("Type 4"), "unexpected detail: {detail}");
1376 }
1377
1378 #[test]
1379 fn empty_buffer_decodes_to_empty() {
1380 assert_eq!(decode_evpn_nlri(&[]).unwrap(), Vec::<EvpnRoute>::new());
1381 }
1382
1383 #[test]
1384 fn route_key_discriminates_ead_per_es_vs_per_evi() {
1385 let per_es = EvpnRoute::EadPerEs(EvpnEadPerEs {
1386 rd: sample_rd(),
1387 esi: sample_esi(),
1388 ethernet_tag: EthernetTagId::MAX_ET,
1389 label: MplsLabel::new(500),
1390 });
1391 let per_evi = EvpnRoute::EadPerEvi(EvpnEadPerEvi {
1392 rd: sample_rd(),
1393 esi: sample_esi(),
1394 ethernet_tag: EthernetTagId(200),
1395 label: MplsLabel::new(500),
1396 });
1397 assert_ne!(per_es.key(), per_evi.key());
1398 }
1399
1400 #[test]
1405 fn ead_per_es_encode_round_trips_to_per_es() {
1406 let r = EvpnRoute::EadPerEs(EvpnEadPerEs {
1407 rd: sample_rd(),
1408 esi: sample_esi(),
1409 ethernet_tag: EthernetTagId::MAX_ET,
1410 label: MplsLabel::new(7),
1411 });
1412 let mut buf = Vec::new();
1413 encode_evpn_nlri(std::slice::from_ref(&r), &mut buf);
1414 let decoded = decode_evpn_nlri(&buf).unwrap();
1415 assert_eq!(decoded.len(), 1);
1416 assert!(matches!(decoded[0], EvpnRoute::EadPerEs(_)));
1417 }
1418
1419 #[test]
1424 #[should_panic(expected = "gateway family must match prefix family")]
1425 fn type5_encode_panics_on_family_mismatch_in_debug() {
1426 let r = EvpnRoute::IpPrefix(EvpnIpPrefixRoute {
1427 rd: sample_rd(),
1428 esi: EthernetSegmentIdentifier::ZERO,
1429 ethernet_tag: EthernetTagId(0),
1430 prefix: EvpnIpPrefixValue::V4(Ipv4Prefix::new(Ipv4Addr::new(10, 0, 0, 0), 8)),
1431 gateway: IpAddr::V6(Ipv6Addr::LOCALHOST),
1432 label: MplsLabel::new(100),
1433 });
1434 let mut buf = Vec::new();
1435 encode_evpn_nlri(&[r], &mut buf);
1436 }
1437}