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", from_py_object))]
46 #[cfg_attr(feature = "wasm-bindgen", $crate::__private::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = __wasm_bindgen))]
47 #[repr(transparent)]
48 pub struct $name(pub(crate) [::core::primitive::u8; $n]);
49 }
50 }
51
52 #[allow(unexpected_cfgs)]
53 const _: () = {
54 impl ::core::default::Default for $name {
55 #[inline]
56 fn default() -> Self {
57 $name::new()
58 }
59 }
60
61 impl $name {
62 pub const SIZE: ::core::primitive::usize = $n;
64
65 #[inline]
67 pub const fn new() -> Self {
68 $name([0; $n])
69 }
70
71 #[inline]
73 pub const fn from_raw(addr: [::core::primitive::u8; $n]) -> Self {
74 $name(addr)
75 }
76
77 #[inline]
79 pub const fn as_bytes(&self) -> &[::core::primitive::u8] {
80 &self.0
81 }
82
83 #[inline]
85 pub const fn octets(&self) -> [::core::primitive::u8; $n] {
86 self.0
87 }
88
89 #[inline]
94 pub const fn to_colon_separated_array(&self) -> [::core::primitive::u8; $n * 3 - 1] {
95 let mut buf = [0u8; $n * 3 - 1];
96 let mut i = 0;
97
98 while i < $n {
99 if i > 0 {
100 buf[i * 3 - 1] = b':';
101 }
102
103 buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
104 buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
105 i += 1;
106 }
107
108 buf
109 }
110
111 #[inline]
116 pub const fn to_hyphen_separated_array(&self) -> [::core::primitive::u8; $n * 3 - 1] {
117 let mut buf = [0u8; $n * 3 - 1];
118 let mut i = 0;
119
120 while i < $n {
121 if i > 0 {
122 buf[i * 3 - 1] = b'-';
123 }
124
125 buf[i * 3] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
126 buf[i * 3 + 1] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
127 i += 1;
128 }
129
130 buf
131 }
132
133 #[inline]
138 pub const fn to_dot_separated_array(&self) -> [::core::primitive::u8; $n * 2 + ($n / 2 - 1)] {
139 let mut buf = [0u8; $n * 2 + ($n / 2 - 1)];
140 let mut i = 0;
141
142 while i < $n {
143 buf[i * 2 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] >> 4) as ::core::primitive::usize];
145 buf[i * 2 + 1 + i / 2] = $crate::__private::HEX_DIGITS[(self.0[i] & 0xF) as ::core::primitive::usize];
147
148 if i % 2 == 1 && i != $n - 1 {
150 buf[i * 2 + 2 + i / 2] = b'.';
151 }
152 i += 1;
153 }
154
155 buf
156 }
157
158 #[cfg(any(feature = "alloc", feature = "std"))]
160 #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
161 pub fn to_colon_separated(&self) -> $crate::__private::String {
162 let buf = self.to_colon_separated_array();
163 unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
165 }
166
167 #[cfg(any(feature = "alloc", feature = "std"))]
169 #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
170 pub fn to_hyphen_separated(&self) -> $crate::__private::String {
171 let buf = self.to_hyphen_separated_array();
172 unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
174 }
175
176 #[cfg(any(feature = "alloc", feature = "std"))]
178 #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
179 pub fn to_dot_separated(&self) -> $crate::__private::String {
180 let buf = self.to_dot_separated_array();
181 unsafe { $crate::__private::ToString::to_string(::core::str::from_utf8_unchecked(&buf)) }
183 }
184 }
185
186 impl ::core::str::FromStr for $name {
187 type Err = $crate::__private::paste::paste! { [< Parse $name Error >] };
188
189 #[inline]
190 fn from_str(src: &str) -> ::core::result::Result<Self, Self::Err> {
191 $crate::parse::<$n>(src.as_bytes()).map(Self)
192 }
193 }
194
195 impl ::core::cmp::PartialEq<[::core::primitive::u8]> for $name {
196 #[inline]
197 fn eq(&self, other: &[::core::primitive::u8]) -> bool {
198 self.0.eq(other)
199 }
200 }
201
202 impl ::core::cmp::PartialEq<$name> for [::core::primitive::u8] {
203 #[inline]
204 fn eq(&self, other: &$name) -> bool {
205 other.eq(self)
206 }
207 }
208
209 impl ::core::cmp::PartialEq<&[::core::primitive::u8]> for $name {
210 #[inline]
211 fn eq(&self, other: &&[::core::primitive::u8]) -> bool {
212 ::core::cmp::PartialEq::eq(self, *other)
213 }
214 }
215
216 impl ::core::cmp::PartialEq<$name> for &[::core::primitive::u8] {
217 #[inline]
218 fn eq(&self, other: &$name) -> bool {
219 ::core::cmp::PartialEq::eq(*self, other)
220 }
221 }
222
223 impl ::core::borrow::Borrow<[::core::primitive::u8]> for $name {
224 #[inline]
225 fn borrow(&self) -> &[::core::primitive::u8] {
226 self
227 }
228 }
229
230 impl ::core::ops::Deref for $name {
231 type Target = [::core::primitive::u8];
232
233 #[inline]
234 fn deref(&self) -> &Self::Target {
235 self.as_bytes()
236 }
237 }
238
239 impl ::core::convert::AsRef<[::core::primitive::u8]> for $name {
240 #[inline]
241 fn as_ref(&self) -> &[::core::primitive::u8] {
242 ::core::borrow::Borrow::borrow(self)
243 }
244 }
245
246 impl ::core::convert::From<[::core::primitive::u8; $n]> for $name {
247 #[inline]
248 fn from(addr: [::core::primitive::u8; $n]) -> Self {
249 $name(addr)
250 }
251 }
252
253 impl ::core::convert::From<$name> for [::core::primitive::u8; $n] {
254 #[inline]
255 #[allow(unexpected_cfgs)]
256 fn from(addr: $name) -> Self {
257 addr.0
258 }
259 }
260
261 impl ::core::convert::TryFrom<&str> for $name {
262 type Error = $crate::__private::paste::paste! { [< Parse $name Error >] };
263
264 #[inline]
265 fn try_from(src: &str) -> ::core::result::Result<Self, Self::Error> {
266 <$name as ::core::str::FromStr>::from_str(src)
267 }
268 }
269
270 impl ::core::fmt::Debug for $name {
271 #[inline]
272 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
273 ::core::fmt::Display::fmt(self, f)
274 }
275 }
276
277 impl core::fmt::Display for $name {
278 #[inline]
279 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280 let buf = self.to_colon_separated_array();
281 write!(
282 f,
283 "{}",
284 unsafe { ::core::str::from_utf8_unchecked(&buf) },
286 )
287 }
288 }
289 };
290
291 #[cfg(feature = "serde")]
292 const _: () = {
293 impl $crate::__private::serde::Serialize for $name {
294 fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
295 where
296 S: $crate::__private::serde::Serializer,
297 {
298 if serializer.is_human_readable() {
299 let buf = self.to_colon_separated_array();
300 serializer.serialize_str(unsafe { ::core::str::from_utf8_unchecked(&buf) })
302 } else {
303 <[::core::primitive::u8; $n] as $crate::__private::serde::Serialize>::serialize(&self.0, serializer)
304 }
305 }
306 }
307
308 impl<'a> $crate::__private::serde::Deserialize<'a> for $name {
309 fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
310 where
311 D: $crate::__private::serde::Deserializer<'a>,
312 {
313 if deserializer.is_human_readable() {
314 let s = <&str as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
315 <$name as ::core::str::FromStr>::from_str(s).map_err($crate::__private::serde::de::Error::custom)
316 } else {
317 let bytes = <[::core::primitive::u8; $n] as $crate::__private::serde::Deserialize>::deserialize(deserializer)?;
318 ::core::result::Result::Ok($name(bytes))
319 }
320 }
321 }
322 };
323
324 #[cfg(feature = "arbitrary")]
325 $crate::__addr_ty_arbitrary! { $name[$n] }
326
327 #[cfg(feature = "quickcheck")]
328 $crate::__addr_ty_quickcheck! { $name[$n] }
329
330 #[cfg(feature = "pyo3")]
331 $crate::__addr_ty_pyo3! { $name[$n] }
332
333 #[cfg(feature = "wasm-bindgen")]
334 $crate::__addr_ty_wasm_bindgen! { $name[$n] }
335 }
336}
337
338mod mac;
339pub use mac::*;
340
341mod eui64;
342pub use eui64::*;
343
344mod infini_band;
345pub use infini_band::*;
346
347#[cfg(feature = "pyo3")]
348mod py;
349#[cfg(feature = "wasm-bindgen")]
350mod wasm;
351
352#[cfg(feature = "arbitrary")]
353mod arbitrary;
354
355#[cfg(feature = "quickcheck")]
356mod quickcheck;
357
358#[doc(hidden)]
359pub mod __private {
360 pub const HEX_DIGITS: [::core::primitive::u8; 16] = *b"0123456789abcdef";
362
363 pub const HEX_VAL: [::core::primitive::u8; 256] = {
367 let mut t = [0xFFu8; 256];
368 let mut i = 0;
369 while i < 10 {
370 t[b'0' as usize + i] = i as u8;
371 i += 1;
372 }
373 let mut i = 0;
374 while i < 6 {
375 t[b'a' as usize + i] = (i + 10) as u8;
376 t[b'A' as usize + i] = (i + 10) as u8;
377 i += 1;
378 }
379 t
380 };
381
382 #[inline]
387 pub const fn hex_byte(
388 hi: ::core::primitive::u8,
389 lo: ::core::primitive::u8,
390 ) -> ::core::option::Option<::core::primitive::u8> {
391 let hi_val = HEX_VAL[hi as usize];
392 if hi_val == 0xFF {
393 return ::core::option::Option::None;
394 }
395 let lo_val = HEX_VAL[lo as usize];
396 if lo_val == 0xFF {
397 return ::core::option::Option::None;
398 }
399 ::core::option::Option::Some((hi_val << 4) | lo_val)
400 }
401
402 #[cfg(feature = "serde")]
403 pub use serde;
404
405 #[cfg(feature = "arbitrary")]
406 pub use arbitrary;
407
408 #[cfg(feature = "quickcheck")]
409 pub use quickcheck;
410
411 #[cfg(feature = "pyo3")]
412 pub use pyo3;
413
414 #[cfg(all(feature = "pyo3", feature = "std"))]
415 pub use std::hash::DefaultHasher;
416 #[cfg(all(feature = "pyo3", not(feature = "std")))]
417 pub type DefaultHasher = ::core::hash::BuildHasherDefault<::core::hash::SipHasher>;
418
419 #[cfg(feature = "wasm-bindgen")]
420 pub use wasm_bindgen;
421
422 #[cfg(any(feature = "alloc", feature = "std"))]
423 pub use std::{
424 boxed::Box,
425 string::{String, ToString},
426 vec::Vec,
427 };
428
429 pub use paste;
430}
431
432#[inline]
445pub const fn xtoi(bytes: &[::core::primitive::u8]) -> Option<(i32, ::core::primitive::usize)> {
446 let mut n: u32 = 0;
450 let mut idx = 0;
451 let num_bytes = bytes.len();
452
453 while idx < num_bytes {
454 let digit = __private::HEX_VAL[bytes[idx] as usize];
455 if digit == 0xFF {
456 break;
457 }
458
459 n = match n.checked_mul(16) {
460 Some(v) => v,
461 None => return None,
462 };
463 n = match n.checked_add(digit as u32) {
464 Some(v) => v,
465 None => return None,
466 };
467 if n > i32::MAX as u32 {
468 return None;
469 }
470
471 idx += 1;
472 }
473
474 if idx == 0 {
475 return None;
476 }
477
478 Some((n as i32, idx))
479}
480
481#[inline]
487pub const fn xtoi2(s: &[u8], e: u8) -> Option<::core::primitive::u8> {
488 if s.len() < 2 {
489 return None;
490 }
491 if s.len() > 2 && s[2] != e {
492 return None;
493 }
494 __private::hex_byte(s[0], s[1])
495}
496
497#[inline]
498const fn dot_separated_format_len<const N: ::core::primitive::usize>() -> ::core::primitive::usize {
499 N * 2 + (N / 2 - 1)
500}
501
502#[inline]
503const fn colon_separated_format_len<const N: ::core::primitive::usize>() -> ::core::primitive::usize
504{
505 N * 3 - 1
506}
507
508#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
510pub enum ParseError<const N: ::core::primitive::usize> {
511 #[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>())]
513 InvalidLength(::core::primitive::usize),
514 #[error("unexpected separator: expected {expected}, but got {actual}")]
516 UnexpectedSeparator {
517 expected: u8,
519 actual: u8,
521 },
522 #[error("invalid separator: {0}")]
524 InvalidSeparator(u8),
525 #[error("invalid digit: {0:?}")]
527 InvalidHexDigit([::core::primitive::u8; 2]),
528}
529
530impl<const N: ::core::primitive::usize> ParseError<N> {
531 #[inline]
533 pub const fn invalid_length(len: ::core::primitive::usize) -> Self {
534 Self::InvalidLength(len)
535 }
536
537 #[inline]
539 pub const fn unexpected_separator(expected: u8, actual: u8) -> Self {
540 Self::UnexpectedSeparator { expected, actual }
541 }
542
543 #[inline]
545 pub const fn invalid_separator(sep: u8) -> Self {
546 Self::InvalidSeparator(sep)
547 }
548
549 #[inline]
551 pub const fn invalid_hex_digit(digit: [::core::primitive::u8; 2]) -> Self {
552 Self::InvalidHexDigit(digit)
553 }
554}
555
556pub const fn parse<const N: ::core::primitive::usize>(
571 src: &[u8],
572) -> Result<[::core::primitive::u8; N], ParseError<N>> {
573 let dot_separated_len = dot_separated_format_len::<N>();
574 let colon_separated_len = colon_separated_format_len::<N>();
575 let len = src.len();
576
577 if len == dot_separated_len {
578 let mut hw = [0u8; N];
579 let mut x = 0usize;
580 let mut i = 0usize;
581
582 while i < N {
583 if x + 4 < len && src[x + 4] != b'.' {
586 return Err(ParseError::unexpected_separator(b'.', src[x + 4]));
587 }
588
589 match __private::hex_byte(src[x], src[x + 1]) {
590 Some(byte) => hw[i] = byte,
591 None => return Err(ParseError::invalid_hex_digit([src[x], src[x + 1]])),
592 }
593 match __private::hex_byte(src[x + 2], src[x + 3]) {
594 Some(byte) => hw[i + 1] = byte,
595 None => return Err(ParseError::invalid_hex_digit([src[x + 2], src[x + 3]])),
596 }
597
598 x += 5;
599 i += 2;
600 }
601
602 return Ok(hw);
603 }
604
605 if len == colon_separated_len {
606 let sep = src[2];
607 if sep != b':' && sep != b'-' {
608 return Err(ParseError::invalid_separator(sep));
609 }
610
611 let mut hw = [0u8; N];
612 let mut x = 0usize;
613 let mut i = 0usize;
614
615 while i < N {
616 if x + 2 < len {
617 let csep = src[x + 2];
618 if csep != sep {
619 return Err(ParseError::unexpected_separator(sep, csep));
620 }
621 }
622
623 match __private::hex_byte(src[x], src[x + 1]) {
624 Some(byte) => hw[i] = byte,
625 None => return Err(ParseError::invalid_hex_digit([src[x], src[x + 1]])),
626 }
627
628 x += 3;
629 i += 1;
630 }
631
632 return Ok(hw);
633 }
634
635 Err(ParseError::invalid_length(len))
636}
637
638#[cfg(test)]
639struct TestCase<const N: ::core::primitive::usize> {
640 input: &'static str,
641 output: Option<std::vec::Vec<::core::primitive::u8>>,
642 err: Option<ParseError<N>>,
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648
649 #[test]
650 fn test_xtoi() {
651 assert_eq!(xtoi(b""), None);
652 assert_eq!(xtoi(b"0"), Some((0, 1)));
653 assert_eq!(xtoi(b"12"), Some((0x12, 2)));
654 assert_eq!(xtoi(b"1a"), Some((0x1a, 2)));
655 assert_eq!(xtoi(b"1A"), Some((0x1a, 2)));
656 assert_eq!(xtoi(b"12x"), Some((0x12, 2)));
657 assert_eq!(xtoi(b"x12"), None);
658 }
659
660 #[test]
661 fn test_xtoi2() {
662 assert_eq!(xtoi2(b"12", b'\0'), Some(0x12));
663 assert_eq!(xtoi2(b"12x", b'x'), Some(0x12));
664 assert_eq!(xtoi2(b"12y", b'x'), None);
665 assert_eq!(xtoi2(b"1", b'\0'), None);
666 assert_eq!(xtoi2(b"xy", b'\0'), None);
667 }
668
669 #[test]
675 fn test_xtoi_overflow_is_detected() {
676 assert_eq!(xtoi(b"FFFFFFFF"), None);
678 assert_eq!(xtoi(b"FFFFFFF"), Some((0x0FFF_FFFF, 7)));
680 assert_eq!(xtoi(b"7FFFFFFF"), Some((0x7FFF_FFFF, 8)));
682 assert_eq!(xtoi(b"80000000"), None);
684 assert_eq!(xtoi(b"0123456789ABCDEF"), None);
686 }
687
688 #[test]
691 fn test_parse_is_const() {
692 const MAC: [u8; 6] = match parse::<6>(b"00:11:22:33:44:55") {
693 Ok(v) => v,
694 Err(_) => panic!(),
695 };
696 assert_eq!(MAC, [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]);
697
698 const MAC2: [u8; 6] = match parse::<6>(b"aa-bb-cc-dd-ee-ff") {
700 Ok(v) => v,
701 Err(_) => panic!(),
702 };
703 assert_eq!(MAC2, [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]);
704
705 const MAC3: [u8; 6] = match parse::<6>(b"0000.5e00.5301") {
707 Ok(v) => v,
708 Err(_) => panic!(),
709 };
710 assert_eq!(MAC3, [0x00, 0x00, 0x5E, 0x00, 0x53, 0x01]);
711 }
712
713 #[test]
716 fn test_hex_byte() {
717 use crate::__private::hex_byte;
718
719 assert_eq!(hex_byte(b'0', b'0'), Some(0x00));
720 assert_eq!(hex_byte(b'f', b'f'), Some(0xFF));
721 assert_eq!(hex_byte(b'F', b'F'), Some(0xFF));
722 assert_eq!(hex_byte(b'1', b'a'), Some(0x1A));
723 assert_eq!(hex_byte(b'1', b'A'), Some(0x1A));
724
725 assert_eq!(hex_byte(b'g', b'0'), None);
727 assert_eq!(hex_byte(b'/', b'0'), None); assert_eq!(hex_byte(b':', b'0'), None); assert_eq!(hex_byte(b'0', b'g'), None);
731 assert_eq!(hex_byte(0, 0), None);
733 }
734}