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 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 pub const SIZE: ::core::primitive::usize = $n;
59
60 #[inline]
62 pub const fn new() -> Self {
63 $name([0; $n])
64 }
65
66 #[inline]
68 pub const fn from_raw(addr: [::core::primitive::u8; $n]) -> Self {
69 $name(addr)
70 }
71
72 #[inline]
74 pub const fn as_bytes(&self) -> &[::core::primitive::u8] {
75 &self.0
76 }
77
78 #[inline]
80 pub const fn octets(&self) -> [::core::primitive::u8; $n] {
81 self.0
82 }
83
84 #[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 #[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 #[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 buf[i * 2 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
140 buf[i * 2 + 1 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
142
143 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 #[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 unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
160 }
161
162 #[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 unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
169 }
170
171 #[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 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 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 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
387const BIG: i32 = 0x7fffffff;
389
390#[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#[inline]
440pub const fn xtoi2(s: &[u8], e: u8) -> Option<::core::primitive::u8> {
441 let num_bytes = s.len();
443
444 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#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
475pub enum ParseError<const N: ::core::primitive::usize> {
476 #[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 #[error("unexpected separator: expected {expected}, but got {actual}")]
481 UnexpectedSeparator {
482 expected: u8,
484 actual: u8,
486 },
487 #[error("invalid separator: {0}")]
489 InvalidSeparator(u8),
490 #[error("invalid digit: {0:?}")]
492 InvalidHexDigit([::core::primitive::u8; 2]),
493}
494
495impl<const N: ::core::primitive::usize> ParseError<N> {
496 #[inline]
498 pub const fn invalid_length(len: ::core::primitive::usize) -> Self {
499 Self::InvalidLength(len)
500 }
501
502 #[inline]
504 pub const fn unexpected_separator(expected: u8, actual: u8) -> Self {
505 Self::UnexpectedSeparator { expected, actual }
506 }
507
508 #[inline]
510 pub const fn invalid_separator(sep: u8) -> Self {
511 Self::InvalidSeparator(sep)
512 }
513
514 #[inline]
516 pub const fn invalid_hex_digit(digit: [::core::primitive::u8; 2]) -> Self {
517 Self::InvalidHexDigit(digit)
518 }
519}
520
521pub 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}