hardware_address/
lib.rs

1// The code is inspired by https://github.com/golang/go/blob/master/src/net/mac.go
2
3#![doc = include_str!("../README.md")]
4#![cfg_attr(not(feature = "std"), no_std)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(docsrs, allow(unused_attributes))]
7#![deny(missing_docs)]
8
9#[cfg(feature = "std")]
10extern crate std;
11
12#[cfg(all(feature = "alloc", not(feature = "std")))]
13#[allow(unused_extern_crates)]
14extern crate alloc as std;
15
16/// A macro for defining address types.
17#[macro_export]
18macro_rules! addr_ty {
19  (
20    $(#[$attr:meta])*
21    $name:ident[$n:expr]
22  ) => {
23
24    $crate::__private::paste::paste! {
25      #[doc = "Represents an error that occurred while parsing `" $name "`."]
26      pub type [< Parse $name Error >] = $crate::ParseError<$n>;
27    }
28
29    $(#[$attr])*
30    #[derive(
31      ::core::clone::Clone,
32      ::core::marker::Copy,
33    )]
34    #[repr(transparent)]
35    pub struct $name([u8; $n]);
36
37    impl ::core::str::FromStr for $name {
38      type Err = $crate::__private::paste::paste! { [< Parse $name Error >] };
39
40      #[inline]
41      fn from_str(src: &str) -> Result<Self, Self::Err> {
42        $crate::parse::<$n>(src).map(Self)
43      }
44    }
45
46    impl $name {
47      /// Creates from a byte array.
48      #[inline]
49      pub const fn new(addr: [u8; $n]) -> Self {
50        $name(addr)
51      }
52
53      /// Returns the address as a byte slice.
54      #[inline]
55      pub const fn as_bytes(&self) -> &[u8] {
56        &self.0
57      }
58
59      /// Returns the octets of the address.
60      #[inline]
61      pub const fn octets(&self) -> [u8; $n] {
62        self.0
63      }
64
65      /// Returns an array contains a colon formatted address.
66      ///
67      /// The returned array can be used to directly convert to `str`
68      /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
69      pub const fn to_colon_seperated_array(&self) -> [u8; $n * 3 - 1] {
70        let mut buf = [0u8; $n * 3 - 1];
71        let mut i = 0;
72
73        while i < $n {
74          if i > 0 {
75            buf[i * 3 - 1] = b':';
76          }
77
78          buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as usize];
79          buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as usize];
80          i += 1;
81        }
82
83        buf
84      }
85
86      /// Returns an array contains a hyphen formatted address.
87      ///
88      /// The returned array can be used to directly convert to `str`
89      /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
90      pub const fn to_hyphen_seperated_array(&self) -> [u8; $n * 3 - 1] {
91        let mut buf = [0u8; $n * 3 - 1];
92        let mut i = 0;
93
94        while i < $n {
95          if i > 0 {
96            buf[i * 3 - 1] = b'-';
97          }
98
99          buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as usize];
100          buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as usize];
101          i += 1;
102        }
103
104        buf
105      }
106
107      /// Returns an array contains a dot formatted address.
108      ///
109      /// The returned array can be used to directly convert to `str`
110      /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
111      pub const fn to_dot_seperated_array(&self) -> [u8; $n * 2 + ($n / 2 - 1)] {
112        let mut buf = [0u8; $n * 2 + ($n / 2 - 1)];
113        let mut i = 0;
114
115        while i < $n {
116          // Convert first nibble to hex char
117          buf[i * 2 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as usize];
118          // Convert second nibble to hex char
119          buf[i * 2 + 1 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as usize];
120
121          // Add dot every 2 bytes except for the last group
122          if i % 2 == 1 && i != $n - 1 {
123            buf[i * 2 + 2 + i / 2] = b'.';
124          }
125          i += 1;
126        }
127
128        buf
129      }
130    }
131
132    impl ::core::convert::AsRef<[u8]> for $name {
133      #[inline]
134      fn as_ref(&self) -> &[u8] {
135        self.as_bytes()
136      }
137    }
138
139    impl ::core::convert::From<[u8; $n]> for $name {
140      #[inline]
141      fn from(addr: [u8; $n]) -> Self {
142        $name(addr)
143      }
144    }
145
146    impl ::core::convert::From<$name> for [u8; $n] {
147      #[inline]
148      fn from(addr: $name) -> Self {
149        addr.0
150      }
151    }
152
153    impl ::core::convert::TryFrom<&str> for $name {
154      type Error = $crate::__private::paste::paste! { [< Parse $name Error >] };
155
156      fn try_from(src: &str) -> ::core::result::Result<Self, Self::Error> {
157        <$name as ::core::str::FromStr>::from_str(src)
158      }
159    }
160
161    impl ::core::fmt::Debug for $name {
162      fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
163        ::core::fmt::Display::fmt(self, f)
164      }
165    }
166
167    impl core::fmt::Display for $name {
168      fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
169        let buf = self.to_colon_seperated_array();
170        write!(
171          f,
172          "{}",
173          core::str::from_utf8(&buf).unwrap(),
174        )
175      }
176    }
177
178    #[cfg(feature = "serde")]
179    const _: () = {
180      impl $crate::__private::serde::Serialize for $name {
181        fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
182        where
183          S: $crate::__private::serde::Serializer,
184        {
185          if serializer.is_human_readable() {
186            let buf = self.to_colon_seperated_array();
187            serializer.serialize_str(::core::str::from_utf8(&buf).unwrap())
188          } else {
189            <[u8; $n] as $crate::__private::serde::Serialize>::serialize(&self.0, serializer)
190          }
191        }
192      }
193
194      impl<'a> $crate::__private::serde::Deserialize<'a> for $name {
195        fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
196        where
197          D: $crate::__private::serde::Deserializer<'a>,
198        {
199          if deserializer.is_human_readable() {
200            let s = <&str as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
201            <$name as ::core::str::FromStr>::from_str(s).map_err($crate::__private::serde::de::Error::custom)
202          } else {
203            let bytes = <[u8; $n] as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
204            Ok($name(bytes))
205          }
206        }
207      }
208    };
209  }
210}
211
212mod mac;
213pub use mac::*;
214
215mod eui64;
216pub use eui64::*;
217
218mod infini_band;
219pub use infini_band::*;
220
221#[doc(hidden)]
222pub mod __private {
223  pub const HEX_DIGITS: &[u8] = b"0123456789abcdef";
224
225  #[cfg(feature = "serde")]
226  pub use serde;
227
228  pub use paste;
229}
230
231/// Maximum value to prevent overflow
232const BIG: i32 = 0x7fffffff;
233
234/// Converts a hexadecimal slice to an integer.
235/// Returns a tuple containing:
236/// - The parsed number
237/// - Number of bytes consumed
238pub const fn xtoi(bytes: &[u8]) -> Option<(i32, usize)> {
239  let mut n: i32 = 0;
240
241  let mut idx = 0;
242  let num_bytes = bytes.len();
243
244  while idx < num_bytes {
245    let c = bytes[idx];
246    match c {
247      b'0'..=b'9' => {
248        n *= 16;
249        n += (c - b'0') as i32;
250      }
251      b'a'..=b'f' => {
252        n *= 16;
253        n += (c - b'a') as i32 + 10;
254      }
255      b'A'..=b'F' => {
256        n *= 16;
257        n += (c - b'A') as i32 + 10;
258      }
259      _ => break,
260    }
261
262    if n == BIG {
263      return None;
264    }
265
266    idx += 1;
267  }
268
269  if idx == 0 {
270    return None;
271  }
272
273  Some((n, idx))
274}
275
276/// Converts the next two hex digits of s into a byte.
277/// If s is longer than 2 bytes then the third byte must match e.
278pub const fn xtoi2(s: &str, e: u8) -> Option<u8> {
279  // Take first two characters and parse them
280  let bytes = s.as_bytes();
281  let num_bytes = bytes.len();
282
283  // Check if string is longer than 2 chars and third char matches e
284  if num_bytes > 2 && bytes[2] != e {
285    return None;
286  }
287
288  let res = if num_bytes >= 2 {
289    let buf = [bytes[0], bytes[1]];
290    xtoi(&buf)
291  } else {
292    xtoi(bytes)
293  };
294
295  match res {
296    Some((n, 2)) => Some(n as u8),
297    _ => None,
298  }
299}
300
301#[inline]
302const fn dot_seperated_format_len<const N: usize>() -> usize {
303  N * 2 + (N / 2 - 1)
304}
305
306#[inline]
307const fn colon_seperated_format_len<const N: usize>() -> usize {
308  N * 3 - 1
309}
310
311/// ParseError represents an error that occurred while parsing hex address.
312#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
313pub enum ParseError<const N: usize> {
314  /// Returned when the input string has a invalid length.
315  #[error("invalid length: colon or hyphen separated format requires {ch_len} bytes, dot separated format requires {dlen} bytes, but got {0} bytes", ch_len = colon_seperated_format_len::<N>(), dlen = dot_seperated_format_len::<N>())]
316  InvalidLength(usize),
317  /// Returned when the input string has an invalid seperator.
318  #[error("unexpected separator: expected {expected}, but got {actual}")]
319  UnexpectedSeparator {
320    /// The expected separator.
321    expected: u8,
322    /// The actual separator.
323    actual: u8,
324  },
325  /// Returned when the input string has an unexpected separator.
326  #[error("invalid separator: {0}")]
327  InvalidSeparator(u8),
328  /// Invalid digit.
329  #[error("invalid digit: {0:?}")]
330  InvalidHexDigit([u8; 2]),
331}
332
333impl<const N: usize> ParseError<N> {
334  /// Returns the length of the address.
335  #[inline]
336  pub const fn invalid_length(len: usize) -> Self {
337    Self::InvalidLength(len)
338  }
339
340  /// Returns an error for an unexpected separator.
341  #[inline]
342  pub const fn unexpected_separator(expected: u8, actual: u8) -> Self {
343    Self::UnexpectedSeparator { expected, actual }
344  }
345
346  /// Returns an error for an invalid separator.
347  #[inline]
348  pub const fn invalid_separator(sep: u8) -> Self {
349    Self::InvalidSeparator(sep)
350  }
351
352  /// Returns an error for an invalid hex digit.
353  #[inline]
354  pub const fn invalid_hex_digit(digit: [u8; 2]) -> Self {
355    Self::InvalidHexDigit(digit)
356  }
357}
358
359/// Parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet
360/// IP over InfiniBand link-layer address and etc using one of the following formats:
361///
362/// - Colon-separated:
363///   - `00:00:5e:00:53:01`
364///   - `02:00:5e:10:00:00:00:01`
365///
366/// - Hyphen-separated:
367///   - `00-00-5e-00-53-01`
368///   - `02-00-5e-10-00-00-00-01`
369///
370/// - Dot-separated:
371///   - `0000.5e00.5301`
372///   - `0200.5e10.0000.0001`
373pub fn parse<const N: usize>(src: &str) -> Result<[u8; N], ParseError<N>> {
374  let dot_seperated_len = dot_seperated_format_len::<N>();
375  let colon_seperated_len = colon_seperated_format_len::<N>();
376  let len = src.len();
377
378  let bytes = src.as_bytes();
379  match () {
380    () if len == dot_seperated_len => {
381      let mut hw = [0; N];
382      let mut x = 0;
383
384      for i in (0..N).step_by(2) {
385        if x + 4 != len && bytes[x + 4] != b'.' {
386          return Err(ParseError::unexpected_separator(b'.', bytes[x + 4]));
387        }
388
389        match xtoi2(&src[x..x + 2], 0) {
390          Some(byte) => hw[i] = byte,
391          None => return Err(ParseError::invalid_hex_digit([bytes[x], bytes[x + 1]])),
392        }
393        match xtoi2(&src[x + 2..], b'.') {
394          Some(byte) => hw[i + 1] = byte,
395          None => return Err(ParseError::invalid_hex_digit([bytes[x + 2], bytes[x + 3]])),
396        }
397
398        x += 5;
399      }
400
401      Ok(hw)
402    }
403    () if len == colon_seperated_len => {
404      let mut hw = [0; N];
405      let mut x = 0;
406
407      let sep = bytes[2];
408      if !(sep == b':' || sep == b'-') {
409        return Err(ParseError::invalid_separator(sep));
410      }
411
412      #[allow(clippy::needless_range_loop)]
413      for i in 0..N {
414        if x + 2 != len {
415          let csep = bytes[x + 2];
416          if csep != sep {
417            return Err(ParseError::unexpected_separator(sep, csep));
418          }
419        }
420
421        match xtoi2(&src[x..], sep) {
422          Some(byte) => hw[i] = byte,
423          None => return Err(ParseError::invalid_hex_digit([bytes[x], bytes[x + 1]])),
424        }
425        x += 3;
426      }
427
428      Ok(hw)
429    }
430    _ => Err(ParseError::invalid_length(len)),
431  }
432}
433
434#[cfg(test)]
435struct TestCase<const N: usize> {
436  input: &'static str,
437  output: Option<std::vec::Vec<u8>>,
438  err: Option<ParseError<N>>,
439}
440
441#[cfg(test)]
442mod tests {
443  use super::*;
444
445  #[test]
446  fn test_xtoi() {
447    assert_eq!(xtoi(b""), None);
448    assert_eq!(xtoi(b"0"), Some((0, 1)));
449    assert_eq!(xtoi(b"12"), Some((0x12, 2)));
450    assert_eq!(xtoi(b"1a"), Some((0x1a, 2)));
451    assert_eq!(xtoi(b"1A"), Some((0x1a, 2)));
452    assert_eq!(xtoi(b"12x"), Some((0x12, 2)));
453    assert_eq!(xtoi(b"x12"), None);
454  }
455
456  #[test]
457  fn test_xtoi2() {
458    assert_eq!(xtoi2("12", b'\0'), Some(0x12));
459    assert_eq!(xtoi2("12x", b'x'), Some(0x12));
460    assert_eq!(xtoi2("12y", b'x'), None);
461    assert_eq!(xtoi2("1", b'\0'), None);
462    assert_eq!(xtoi2("xy", b'\0'), None);
463  }
464}