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    paste::paste! {
24      pub use [< __ $name:snake __ >]::{$name, [< Parse $name Error >]};
25
26      #[doc(hidden)]
27      #[allow(unused)]
28      mod [< __ $name:snake __ >] {
29        #[cfg(feature = "pyo3")]
30        use $crate::__private::pyo3 as __pyo3;
31
32        #[cfg(feature = "wasm-bindgen")]
33        use $crate::__private::wasm_bindgen as __wasm_bindgen;
34
35        #[doc = "Represents an error that occurred while parsing `" $name "`."]
36        pub type [< Parse $name Error >] = $crate::ParseError<$n>;
37
38        $(#[$attr])*
39        #[derive(::core::clone::Clone, ::core::marker::Copy, ::core::cmp::Eq, ::core::cmp::PartialEq, ::core::cmp::Ord, ::core::cmp::PartialOrd, ::core::hash::Hash)]
40        #[cfg_attr(feature = "pyo3", $crate::__private::pyo3::pyclass(crate = "__pyo3"))]
41        #[cfg_attr(feature = "wasm-bindgen", $crate::__private::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = __wasm_bindgen))]
42        #[repr(transparent)]
43        pub struct $name(pub(crate) [::core::primitive::u8; $n]);
44      }
45    }
46
47    #[allow(unexpected_cfgs)]
48    const _: () = {
49      impl ::core::default::Default for $name {
50        #[inline]
51        fn default() -> Self {
52          $name::new()
53        }
54      }
55
56      impl $name {
57        /// The size of the address in bytes.
58        pub const SIZE: ::core::primitive::usize = $n;
59
60        /// Creates a zeroed address.
61        #[inline]
62        pub const fn new() -> Self {
63          $name([0; $n])
64        }
65
66        /// Creates from raw byte array address.
67        #[inline]
68        pub const fn from_raw(addr: [::core::primitive::u8; $n]) -> Self {
69          $name(addr)
70        }
71
72        /// Returns the address as a byte slice.
73        #[inline]
74        pub const fn as_bytes(&self) -> &[::core::primitive::u8] {
75          &self.0
76        }
77
78        /// Returns the octets of the address.
79        #[inline]
80        pub const fn octets(&self) -> [::core::primitive::u8; $n] {
81          self.0
82        }
83
84        /// Returns an array contains a colon formatted address.
85        ///
86        /// The returned array can be used to directly convert to `str`
87        /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
88        #[inline]
89        pub const fn to_colon_separated_array(&self) -> [::core::primitive::u8; $n * 3 - 1] {
90          let mut buf = [0u8; $n * 3 - 1];
91          let mut i = 0;
92
93          while i < $n {
94            if i > 0 {
95              buf[i * 3 - 1] = b':';
96            }
97
98            buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
99            buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
100            i += 1;
101          }
102
103          buf
104        }
105
106        /// Returns an array contains a hyphen formatted address.
107        ///
108        /// The returned array can be used to directly convert to `str`
109        /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
110        #[inline]
111        pub const fn to_hyphen_separated_array(&self) -> [::core::primitive::u8; $n * 3 - 1] {
112          let mut buf = [0u8; $n * 3 - 1];
113          let mut i = 0;
114
115          while i < $n {
116            if i > 0 {
117              buf[i * 3 - 1] = b'-';
118            }
119
120            buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
121            buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
122            i += 1;
123          }
124
125          buf
126        }
127
128        /// Returns an array contains a dot formatted address.
129        ///
130        /// The returned array can be used to directly convert to `str`
131        /// by using [`core::str::from_utf8(&array).unwrap( )`](core::str::from_utf8).
132        #[inline]
133        pub const fn to_dot_separated_array(&self) -> [::core::primitive::u8; $n * 2 + ($n / 2 - 1)] {
134          let mut buf = [0u8; $n * 2 + ($n / 2 - 1)];
135          let mut i = 0;
136
137          while i < $n {
138            // Convert first nibble to hex char
139            buf[i * 2 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
140            // Convert second nibble to hex char
141            buf[i * 2 + 1 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
142
143            // Add dot every 2 bytes except for the last group
144            if i % 2 == 1 && i != $n - 1 {
145              buf[i * 2 + 2 + i / 2] = b'.';
146            }
147            i += 1;
148          }
149
150          buf
151        }
152
153        /// Converts to colon-separated format string.
154        #[cfg(any(feature = "alloc", feature = "std"))]
155        #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
156        pub fn to_colon_separated(&self) -> $crate::__private::String {
157          let buf = self.to_colon_separated_array();
158          // SAFETY: The buffer is always valid UTF-8 as it only contains ASCII characters.
159          unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
160        }
161
162        /// Converts to hyphen-separated format string.
163        #[cfg(any(feature = "alloc", feature = "std"))]
164        #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
165        pub fn to_hyphen_separated(&self) -> $crate::__private::String {
166          let buf = self.to_hyphen_separated_array();
167          // SAFETY: The buffer is always valid UTF-8 as it only contains ASCII characters.
168          unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
169        }
170
171        /// Converts to dot-separated format string.
172        #[cfg(any(feature = "alloc", feature = "std"))]
173        #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
174        pub fn to_dot_separated(&self) -> $crate::__private::String {
175          let buf = self.to_dot_separated_array();
176          // SAFETY: The buffer is always valid UTF-8 as it only contains ASCII characters.
177          unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
178        }
179      }
180
181      impl ::core::str::FromStr for $name {
182        type Err = $crate::__private::paste::paste! { [< Parse $name Error >] };
183
184        #[inline]
185        fn from_str(src: &str) -> ::core::result::Result<Self, Self::Err> {
186          $crate::parse::<$n>(src.as_bytes()).map(Self)
187        }
188      }
189
190      impl ::core::cmp::PartialEq<[::core::primitive::u8]> for $name {
191        #[inline]
192        fn eq(&self, other: &[::core::primitive::u8]) -> bool {
193          self.0.eq(other)
194        }
195      }
196
197      impl ::core::cmp::PartialEq<$name> for [::core::primitive::u8] {
198        #[inline]
199        fn eq(&self, other: &$name) -> bool {
200          other.eq(self)
201        }
202      }
203
204      impl ::core::cmp::PartialEq<&[::core::primitive::u8]> for $name {
205        #[inline]
206        fn eq(&self, other: &&[::core::primitive::u8]) -> bool {
207          ::core::cmp::PartialEq::eq(self, *other)
208        }
209      }
210
211      impl ::core::cmp::PartialEq<$name> for &[::core::primitive::u8] {
212        #[inline]
213        fn eq(&self, other: &$name) -> bool {
214          ::core::cmp::PartialEq::eq(*self, other)
215        }
216      }
217
218      impl ::core::borrow::Borrow<[::core::primitive::u8]> for $name {
219        #[inline]
220        fn borrow(&self) -> &[::core::primitive::u8] {
221          self
222        }
223      }
224
225      impl ::core::ops::Deref for $name {
226        type Target = [::core::primitive::u8];
227
228        #[inline]
229        fn deref(&self) -> &Self::Target {
230          self.as_bytes()
231        }
232      }
233
234      impl ::core::convert::AsRef<[::core::primitive::u8]> for $name {
235        #[inline]
236        fn as_ref(&self) -> &[::core::primitive::u8] {
237          ::core::borrow::Borrow::borrow(self)
238        }
239      }
240
241      impl ::core::convert::From<[::core::primitive::u8; $n]> for $name {
242        #[inline]
243        fn from(addr: [::core::primitive::u8; $n]) -> Self {
244          $name(addr)
245        }
246      }
247
248      impl ::core::convert::From<$name> for [::core::primitive::u8; $n] {
249        #[inline]
250        #[allow(unexpected_cfgs)]
251        fn from(addr: $name) -> Self {
252          addr.0
253        }
254      }
255
256      impl ::core::convert::TryFrom<&str> for $name {
257        type Error = $crate::__private::paste::paste! { [< Parse $name Error >] };
258
259        #[inline]
260        fn try_from(src: &str) -> ::core::result::Result<Self, Self::Error> {
261          <$name as ::core::str::FromStr>::from_str(src)
262        }
263      }
264
265      impl ::core::fmt::Debug for $name {
266        #[inline]
267        fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
268          ::core::fmt::Display::fmt(self, f)
269        }
270      }
271
272      impl core::fmt::Display for $name {
273        #[inline]
274        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
275          let buf = self.to_colon_separated_array();
276          write!(
277            f,
278            "{}",
279            // SAFETY: The buffer is always valid UTF-8 as it only contains ASCII characters.
280            unsafe { ::core::str::from_utf8_unchecked(&buf) },
281          )
282        }
283      }
284    };
285
286    #[cfg(feature = "serde")]
287    const _: () = {
288      impl $crate::__private::serde::Serialize for $name {
289        fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
290        where
291          S: $crate::__private::serde::Serializer,
292        {
293          if serializer.is_human_readable() {
294            let buf = self.to_colon_separated_array();
295            // SAFETY: The buffer is always valid UTF-8 as it only contains ASCII characters.
296            serializer.serialize_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })
297          } else {
298            <[::core::primitive::u8; $n] as $crate::__private::serde::Serialize>::serialize(&self.0, serializer)
299          }
300        }
301      }
302
303      impl<'a> $crate::__private::serde::Deserialize<'a> for $name {
304        fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
305        where
306          D: $crate::__private::serde::Deserializer<'a>,
307        {
308          if deserializer.is_human_readable() {
309            let s = <&str as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
310            <$name as ::core::str::FromStr>::from_str(s).map_err($crate::__private::serde::de::Error::custom)
311          } else {
312            let bytes = <[::core::primitive::u8; $n] as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
313            ::core::result::Result::Ok($name(bytes))
314          }
315        }
316      }
317    };
318
319    #[cfg(feature = "arbitrary")]
320    $crate::__addr_ty_arbitrary! { $name[$n] }
321
322    #[cfg(feature = "quickcheck")]
323    $crate::__addr_ty_quickcheck! { $name[$n] }
324
325    #[cfg(feature = "pyo3")]
326    $crate::__addr_ty_pyo3! { $name[$n] }
327
328    #[cfg(feature = "wasm-bindgen")]
329    $crate::__addr_ty_wasm_bindgen! { $name[$n] }
330  }
331}
332
333mod mac;
334pub use mac::*;
335
336mod eui64;
337pub use eui64::*;
338
339mod infini_band;
340pub use infini_band::*;
341
342#[cfg(feature = "pyo3")]
343mod py;
344#[cfg(feature = "wasm-bindgen")]
345mod wasm;
346
347#[cfg(feature = "arbitrary")]
348mod arbitrary;
349
350#[cfg(feature = "quickcheck")]
351mod quickcheck;
352
353#[doc(hidden)]
354pub mod __private {
355  pub const HEX_DIGITS: &[::core::primitive::u8] = b"0123456789abcdef";
356
357  #[cfg(feature = "serde")]
358  pub use serde;
359
360  #[cfg(feature = "arbitrary")]
361  pub use arbitrary;
362
363  #[cfg(feature = "quickcheck")]
364  pub use quickcheck;
365
366  #[cfg(feature = "pyo3")]
367  pub use pyo3;
368
369  #[cfg(all(feature = "pyo3", feature = "std"))]
370  pub use std::hash::DefaultHasher;
371  #[cfg(all(feature = "pyo3", not(feature = "std")))]
372  pub type DefaultHasher = ::core::hash::BuildHasherDefault<::core::hash::SipHasher>;
373
374  #[cfg(feature = "wasm-bindgen")]
375  pub use wasm_bindgen;
376
377  #[cfg(any(feature = "alloc", feature = "std"))]
378  pub use std::{
379    boxed::Box,
380    string::{String, ToString},
381    vec::Vec,
382  };
383
384  pub use paste;
385}
386
387/// Maximum value to prevent overflow
388const BIG: i32 = 0x7fffffff;
389
390/// Converts a hexadecimal slice to an integer.
391///
392/// - Returns a tuple containing:
393///   - The parsed number
394///   - Number of bytes consumed
395/// - Returns `None` if parsing fails.
396#[inline]
397pub const fn xtoi(bytes: &[::core::primitive::u8]) -> Option<(i32, ::core::primitive::usize)> {
398  let mut n: i32 = 0;
399
400  let mut idx = 0;
401  let num_bytes = bytes.len();
402
403  while idx < num_bytes {
404    let c = bytes[idx];
405    match c {
406      b'0'..=b'9' => {
407        n *= 16;
408        n += (c - b'0') as i32;
409      }
410      b'a'..=b'f' => {
411        n *= 16;
412        n += (c - b'a') as i32 + 10;
413      }
414      b'A'..=b'F' => {
415        n *= 16;
416        n += (c - b'A') as i32 + 10;
417      }
418      _ => break,
419    }
420
421    if n == BIG {
422      return None;
423    }
424
425    idx += 1;
426  }
427
428  if idx == 0 {
429    return None;
430  }
431
432  Some((n, idx))
433}
434
435/// Converts the next two hex digits of s into a byte.
436/// If s is longer than 2 bytes then the third byte must match e.
437///
438/// Returns `None` if parsing fails.
439#[inline]
440pub const fn xtoi2(s: &[u8], e: u8) -> Option<::core::primitive::u8> {
441  // Take first two characters and parse them
442  let num_bytes = s.len();
443
444  // Check if string is longer than 2 chars and third char matches e
445  if num_bytes > 2 && s[2] != e {
446    return None;
447  }
448
449  let res = if num_bytes >= 2 {
450    let buf = [s[0], s[1]];
451    xtoi(&buf)
452  } else {
453    xtoi(s)
454  };
455
456  match res {
457    Some((n, 2)) => Some(n as u8),
458    _ => None,
459  }
460}
461
462#[inline]
463const fn dot_separated_format_len<const N: ::core::primitive::usize>() -> ::core::primitive::usize {
464  N * 2 + (N / 2 - 1)
465}
466
467#[inline]
468const fn colon_separated_format_len<const N: ::core::primitive::usize>() -> ::core::primitive::usize
469{
470  N * 3 - 1
471}
472
473/// ParseError represents an error that occurred while parsing hex address.
474#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
475pub enum ParseError<const N: ::core::primitive::usize> {
476  /// Returned when the input string has a invalid length.
477  #[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_separated_format_len::<N>(), dlen = dot_separated_format_len::<N>())]
478  InvalidLength(::core::primitive::usize),
479  /// Returned when the input string has an invalid seperator.
480  #[error("unexpected separator: expected {expected}, but got {actual}")]
481  UnexpectedSeparator {
482    /// The expected separator.
483    expected: u8,
484    /// The actual separator.
485    actual: u8,
486  },
487  /// Returned when the input string has an unexpected separator.
488  #[error("invalid separator: {0}")]
489  InvalidSeparator(u8),
490  /// Invalid digit.
491  #[error("invalid digit: {0:?}")]
492  InvalidHexDigit([::core::primitive::u8; 2]),
493}
494
495impl<const N: ::core::primitive::usize> ParseError<N> {
496  /// Returns the length of the address.
497  #[inline]
498  pub const fn invalid_length(len: ::core::primitive::usize) -> Self {
499    Self::InvalidLength(len)
500  }
501
502  /// Returns an error for an unexpected separator.
503  #[inline]
504  pub const fn unexpected_separator(expected: u8, actual: u8) -> Self {
505    Self::UnexpectedSeparator { expected, actual }
506  }
507
508  /// Returns an error for an invalid separator.
509  #[inline]
510  pub const fn invalid_separator(sep: u8) -> Self {
511    Self::InvalidSeparator(sep)
512  }
513
514  /// Returns an error for an invalid hex digit.
515  #[inline]
516  pub const fn invalid_hex_digit(digit: [::core::primitive::u8; 2]) -> Self {
517    Self::InvalidHexDigit(digit)
518  }
519}
520
521/// Parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet
522/// IP over InfiniBand link-layer address and etc using one of the following formats:
523///
524/// - Colon-separated:
525///   - `00:00:5e:00:53:01`
526///   - `02:00:5e:10:00:00:00:01`
527///
528/// - Hyphen-separated:
529///   - `00-00-5e-00-53-01`
530///   - `02-00-5e-10-00-00-00-01`
531///
532/// - Dot-separated:
533///   - `0000.5e00.5301`
534///   - `0200.5e10.0000.0001`
535pub fn parse<const N: ::core::primitive::usize>(
536  src: &[u8],
537) -> Result<[::core::primitive::u8; N], ParseError<N>> {
538  let dot_separated_len = dot_separated_format_len::<N>();
539  let colon_separated_len = colon_separated_format_len::<N>();
540  let len = src.len();
541
542  let bytes = src;
543  match () {
544    () if len == dot_separated_len => {
545      let mut hw = [0; N];
546      let mut x = 0;
547
548      for i in (0..N).step_by(2) {
549        if x + 4 != len && bytes[x + 4] != b'.' {
550          return Err(ParseError::unexpected_separator(b'.', bytes[x + 4]));
551        }
552
553        match xtoi2(&src[x..x + 2], 0) {
554          Some(byte) => hw[i] = byte,
555          None => return Err(ParseError::invalid_hex_digit([bytes[x], bytes[x + 1]])),
556        }
557        match xtoi2(&src[x + 2..], b'.') {
558          Some(byte) => hw[i + 1] = byte,
559          None => return Err(ParseError::invalid_hex_digit([bytes[x + 2], bytes[x + 3]])),
560        }
561
562        x += 5;
563      }
564
565      Ok(hw)
566    }
567    () if len == colon_separated_len => {
568      let mut hw = [0; N];
569      let mut x = 0;
570
571      let sep = bytes[2];
572      if !(sep == b':' || sep == b'-') {
573        return Err(ParseError::invalid_separator(sep));
574      }
575
576      #[allow(clippy::needless_range_loop)]
577      for i in 0..N {
578        if x + 2 != len {
579          let csep = bytes[x + 2];
580          if csep != sep {
581            return Err(ParseError::unexpected_separator(sep, csep));
582          }
583        }
584
585        match xtoi2(&src[x..], sep) {
586          Some(byte) => hw[i] = byte,
587          None => return Err(ParseError::invalid_hex_digit([bytes[x], bytes[x + 1]])),
588        }
589        x += 3;
590      }
591
592      Ok(hw)
593    }
594    _ => Err(ParseError::invalid_length(len)),
595  }
596}
597
598#[cfg(test)]
599struct TestCase<const N: ::core::primitive::usize> {
600  input: &'static str,
601  output: Option<std::vec::Vec<::core::primitive::u8>>,
602  err: Option<ParseError<N>>,
603}
604
605#[cfg(test)]
606mod tests {
607  use super::*;
608
609  #[test]
610  fn test_xtoi() {
611    assert_eq!(xtoi(b""), None);
612    assert_eq!(xtoi(b"0"), Some((0, 1)));
613    assert_eq!(xtoi(b"12"), Some((0x12, 2)));
614    assert_eq!(xtoi(b"1a"), Some((0x1a, 2)));
615    assert_eq!(xtoi(b"1A"), Some((0x1a, 2)));
616    assert_eq!(xtoi(b"12x"), Some((0x12, 2)));
617    assert_eq!(xtoi(b"x12"), None);
618  }
619
620  #[test]
621  fn test_xtoi2() {
622    assert_eq!(xtoi2(b"12", b'\0'), Some(0x12));
623    assert_eq!(xtoi2(b"12x", b'x'), Some(0x12));
624    assert_eq!(xtoi2(b"12y", b'x'), None);
625    assert_eq!(xtoi2(b"1", b'\0'), None);
626    assert_eq!(xtoi2(b"xy", b'\0'), None);
627  }
628}