Skip to main content

dlep_core/
mac.rs

1use std::fmt;
2
3/// Destination address format of the modem's router-facing link (§13.7).
4/// This is a local session policy, not a negotiated wire parameter.
5#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
6pub enum MacAddressFormat {
7    #[default]
8    Eui48,
9    Eui64,
10}
11
12impl MacAddressFormat {
13    pub fn accepts(self, mac: MacAddress) -> bool {
14        matches!(
15            (self, mac),
16            (Self::Eui48, MacAddress::Eui48(_)) | (Self::Eui64, MacAddress::Eui64(_))
17        )
18    }
19}
20
21/// MAC address used to identify a DLEP destination. RFC 8175 §13.7 permits
22/// either EUI-48 (6 octets) or EUI-64 (8 octets) on the wire and requires
23/// all destination MACs in a single session to share one format
24/// (consistent with the modem's link-layer format).
25#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)]
26pub enum MacAddress {
27    Eui48([u8; 6]),
28    Eui64([u8; 8]),
29}
30
31impl MacAddress {
32    /// 6-octet broadcast (FF:FF:FF:FF:FF:FF). Use this in EUI-48 sessions.
33    pub const BROADCAST_EUI48: Self = Self::Eui48([0xff; 6]);
34    /// 8-octet broadcast. Use this in EUI-64 sessions.
35    pub const BROADCAST_EUI64: Self = Self::Eui64([0xff; 8]);
36    /// Alias for [`Self::BROADCAST_EUI48`]; preserves the M3-era name.
37    pub const BROADCAST: Self = Self::BROADCAST_EUI48;
38
39    pub const fn new_eui48(octets: [u8; 6]) -> Self {
40        Self::Eui48(octets)
41    }
42
43    pub const fn new_eui64(octets: [u8; 8]) -> Self {
44        Self::Eui64(octets)
45    }
46
47    /// Octets in network byte order. Length is 6 for EUI-48, 8 for EUI-64.
48    pub fn as_bytes(&self) -> &[u8] {
49        match self {
50            MacAddress::Eui48(o) => o,
51            MacAddress::Eui64(o) => o,
52        }
53    }
54
55    /// Wire length in octets (RFC 8175 §13.7: 6 or 8).
56    #[allow(clippy::len_without_is_empty)]
57    pub const fn len(&self) -> usize {
58        match self {
59            MacAddress::Eui48(_) => 6,
60            MacAddress::Eui64(_) => 8,
61        }
62    }
63
64    /// `true` if this is an EUI-48 (6-octet) address.
65    pub const fn is_eui48(&self) -> bool {
66        matches!(self, MacAddress::Eui48(_))
67    }
68
69    /// `true` if this is an EUI-64 (8-octet) address.
70    pub const fn is_eui64(&self) -> bool {
71        matches!(self, MacAddress::Eui64(_))
72    }
73}
74
75impl fmt::Debug for MacAddress {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "MacAddress({})", self)
78    }
79}
80
81impl fmt::Display for MacAddress {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        let mut first = true;
84        for byte in self.as_bytes() {
85            if first {
86                first = false;
87            } else {
88                write!(f, ":")?;
89            }
90            write!(f, "{byte:02x}")?;
91        }
92        Ok(())
93    }
94}
95
96impl From<[u8; 6]> for MacAddress {
97    fn from(v: [u8; 6]) -> Self {
98        Self::Eui48(v)
99    }
100}
101
102impl From<[u8; 8]> for MacAddress {
103    fn from(v: [u8; 8]) -> Self {
104        Self::Eui64(v)
105    }
106}