1#![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#[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 #[inline]
49 pub const fn new(addr: [u8; $n]) -> Self {
50 $name(addr)
51 }
52
53 #[inline]
55 pub const fn as_bytes(&self) -> &[u8] {
56 &self.0
57 }
58
59 #[inline]
61 pub const fn octets(&self) -> [u8; $n] {
62 self.0
63 }
64
65 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 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 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 buf[i * 2 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as usize];
118 buf[i * 2 + 1 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as usize];
120
121 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
231const BIG: i32 = 0x7fffffff;
233
234pub 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
276pub const fn xtoi2(s: &str, e: u8) -> Option<u8> {
279 let bytes = s.as_bytes();
281 let num_bytes = bytes.len();
282
283 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#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
313pub enum ParseError<const N: usize> {
314 #[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 #[error("unexpected separator: expected {expected}, but got {actual}")]
319 UnexpectedSeparator {
320 expected: u8,
322 actual: u8,
324 },
325 #[error("invalid separator: {0}")]
327 InvalidSeparator(u8),
328 #[error("invalid digit: {0:?}")]
330 InvalidHexDigit([u8; 2]),
331}
332
333impl<const N: usize> ParseError<N> {
334 #[inline]
336 pub const fn invalid_length(len: usize) -> Self {
337 Self::InvalidLength(len)
338 }
339
340 #[inline]
342 pub const fn unexpected_separator(expected: u8, actual: u8) -> Self {
343 Self::UnexpectedSeparator { expected, actual }
344 }
345
346 #[inline]
348 pub const fn invalid_separator(sep: u8) -> Self {
349 Self::InvalidSeparator(sep)
350 }
351
352 #[inline]
354 pub const fn invalid_hex_digit(digit: [u8; 2]) -> Self {
355 Self::InvalidHexDigit(digit)
356 }
357}
358
359pub 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}