1#![no_std]
16
17#[cfg(feature = "serde")]
18mod de;
19#[cfg(feature = "serde")]
20mod ser;
21
22use core::convert::TryFrom;
23use core::fmt::{Display, Error, Formatter, LowerHex, UpperHex};
24use heapless::consts::*;
25use heapless::{String, Vec};
26
27const UPPERCASE_HEX_CHARS: &[u8] = b"0123456789ABCDEF";
28
29#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, hash32_derive::Hash32)]
30pub struct Eui48([u8; 6]);
31#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, hash32_derive::Hash32)]
32pub struct Eui64([u8; 8]);
33
34macro_rules! to_hex_string {
35 ($eui: expr, $size: ty) => {{
36 let mut vec = Vec::<u8, $size>::new();
37
38 for (i, &byte) in $eui.0.iter().enumerate() {
39 if i != 0 {
40 vec.push('-' as u8).expect("Vector is not long enough");
41 }
42
43 vec.push(UPPERCASE_HEX_CHARS[(byte >> 4) as usize])
44 .expect("Vector is not long enough");
45
46 vec.push(UPPERCASE_HEX_CHARS[(byte & 0xf) as usize])
47 .expect("Vector is not long enough");
48 }
49
50 unsafe { String::from_utf8_unchecked(vec) }
51 }};
52}
53
54impl Eui48 {
55 #[inline]
56 pub fn to_string(&self) -> String<U17> {
57 to_hex_string!(self, U17)
58 }
59}
60
61impl Eui64 {
62 #[inline]
63 pub fn to_string(&self) -> String<U23> {
64 to_hex_string!(self, U23)
65 }
66}
67
68impl From<u64> for Eui48 {
69 fn from(value: u64) -> Self {
70 let b1: u8 = ((value >> 40) & 0xff) as u8;
71 let b2: u8 = ((value >> 32) & 0xff) as u8;
72 let b3: u8 = ((value >> 24) & 0xff) as u8;
73 let b4: u8 = ((value >> 16) & 0xff) as u8;
74 let b5: u8 = ((value >> 8) & 0xff) as u8;
75 let b6: u8 = (value & 0xff) as u8;
76
77 return Eui48([b1, b2, b3, b4, b5, b6]);
78 }
79}
80
81impl From<u64> for Eui64 {
82 fn from(value: u64) -> Self {
83 Eui64(value.to_be_bytes())
84 }
85}
86
87#[derive(Debug, PartialEq, Eq)]
89pub enum StringToEuiError {
90 InvalidLength { length: usize },
91 InvalidChar { char: char },
92 InvalidSeparatorPlace,
93 OnlyOneSeparatorTypeExpected,
94}
95
96pub(crate) fn string_to_eui(input: &str, result: &mut [u8]) -> Result<(), StringToEuiError> {
97 let mut separator_type = None;
98 let mut separators = 0;
99
100 for (i, c) in input.chars().enumerate() {
101 let char_byte = c as u8;
102
103 let hex_char_index = match char_byte {
104 b'A'..=b'F' => Some(char_byte - b'A' + 10),
105 b'a'..=b'f' => Some(char_byte - b'a' + 10),
106 b'0'..=b'9' => Some(char_byte - b'0'),
107 _ => None,
108 };
109
110 match hex_char_index {
111 Some(value) => {
112 let current_pos = i - separators;
113 let index = current_pos / 2;
114
115 if index > result.len() - 1 {
116 return Err(StringToEuiError::InvalidLength {
117 length: input.len() - separators,
118 });
119 }
120
121 if current_pos % 2 == 0 {
122 result[index] = (value as u8) << 4 & 0xF0
123 } else {
124 result[index] |= value as u8 & 0xF
125 }
126 }
127 None if c == ':' || c == '-' => {
128 if i == 0 || i == input.len() || (i + 1) % 3 != 0 {
130 return Err(StringToEuiError::InvalidSeparatorPlace);
131 }
132
133 match separator_type {
134 Some(t) => {
135 if t != c {
136 return Err(StringToEuiError::OnlyOneSeparatorTypeExpected);
137 }
138 }
139 None => separator_type = Some(c),
140 }
141
142 separators += 1;
143 }
144 None => {
145 return Err(StringToEuiError::InvalidChar { char: c });
146 }
147 }
148 }
149
150 Ok(())
151}
152
153impl TryFrom<&str> for Eui48 {
154 type Error = StringToEuiError;
155
156 fn try_from(value: &str) -> Result<Self, Self::Error> {
157 if value.len() != 12 && value.len() != 17 {
158 return Err(StringToEuiError::InvalidLength {
159 length: value.len(),
160 });
161 }
162
163 let mut result = [0; 6];
164 string_to_eui(value, &mut result[..])?;
165
166 Ok(Eui48(result))
167 }
168}
169
170impl TryFrom<&str> for Eui64 {
171 type Error = StringToEuiError;
172
173 fn try_from(value: &str) -> Result<Self, Self::Error> {
174 if value.len() != 16 && value.len() != 23 {
175 return Err(StringToEuiError::InvalidLength {
176 length: value.len(),
177 });
178 }
179
180 let mut result = [0; 8];
181 string_to_eui(value, &mut result[..])?;
182
183 Ok(Eui64(result))
184 }
185}
186
187impl From<Eui48> for Eui64 {
188 fn from(eui48: Eui48) -> Self {
189 let mut data = [0u8; 8];
190
191 for i in 0..3 {
192 data[i] = eui48.0[i]
193 }
194
195 for i in 5..8 {
196 data[i] = eui48.0[i - 2]
197 }
198
199 Eui64(data)
200 }
201}
202
203impl From<Eui48> for u64 {
204 fn from(eui48: Eui48) -> Self {
205 let data = eui48.0;
206
207 ((data[0] as u64) << 40)
208 + ((data[1] as u64) << 32)
209 + ((data[2] as u64) << 24)
210 + ((data[3] as u64) << 16)
211 + ((data[4] as u64) << 8)
212 + ((data[5] as u64) << 0)
213 }
214}
215
216impl From<Eui64> for u64 {
217 fn from(eui64: Eui64) -> Self {
218 u64::from_be_bytes(eui64.0)
219 }
220}
221
222impl Display for Eui48 {
223 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
224 write!(f, "{}", self.to_string())
225 }
226}
227
228impl Display for Eui64 {
229 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
230 write!(f, "{}", self.to_string())
231 }
232}
233
234impl UpperHex for Eui48 {
235 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
236 write!(f, "{:X}", u64::from(*self))
237 }
238}
239
240impl LowerHex for Eui48 {
241 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
242 write!(f, "{:x}", u64::from(*self))
243 }
244}
245
246impl UpperHex for Eui64 {
247 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
248 write!(f, "{:X}", u64::from(*self))
249 }
250}
251
252impl LowerHex for Eui64 {
253 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
254 write!(f, "{:x}", u64::from(*self))
255 }
256}
257
258#[test]
259fn test_eui48_to_string() {
260 let eui48 = Eui48::from(85204980412143);
261
262 assert_eq!(eui48.to_string(), "4D-7E-54-97-2E-EF")
263}
264
265#[test]
266fn test_eui64_to_string() {
267 let eui64 = Eui64::from(5583992946972634863);
268
269 assert_eq!(eui64.to_string(), "4D-7E-54-00-00-97-2E-EF")
270}
271
272#[test]
273fn test_eui48_to_eui64() {
274 let eui48 = Eui48::from(85204980412143);
275 let eui64 = Eui64::from(eui48);
276
277 assert_eq!(eui64.to_string(), "4D-7E-54-00-00-97-2E-EF")
278}
279
280#[test]
281fn test_u64_from_eui48() {
282 let eui48 = Eui48::from(85204980412143);
283 assert_eq!(u64::from(eui48), 85204980412143);
284}
285
286#[test]
287fn test_u64_from_eui64() {
288 let eui64 = Eui64::from(5583992946972634863);
289 assert_eq!(u64::from(eui64), 5583992946972634863);
290}
291
292#[test]
293fn test_hash_eui48() {
294 use heapless::FnvIndexMap;
295
296 let eui48 = Eui48::from(85204980412143);
297
298 let mut fnv_index_map: FnvIndexMap<Eui48, u8, U1> = FnvIndexMap::new();
299 fnv_index_map.insert(eui48, 1).unwrap();
300
301 assert_eq!(1, *fnv_index_map.get(&eui48).unwrap())
302}
303
304#[test]
305fn test_hash_eui64() {
306 use heapless::FnvIndexMap;
307
308 let eui64 = Eui64::from(5583992946972634863);
309
310 let mut fnv_index_map: FnvIndexMap<Eui64, u8, U1> = FnvIndexMap::new();
311 fnv_index_map.insert(eui64, 1).unwrap();
312
313 assert_eq!(1, *fnv_index_map.get(&eui64).unwrap())
314}
315
316#[test]
317fn test_display_eui48() {
318 extern crate std;
319 use std::format;
320
321 let eui48 = Eui48::from(85204980412143);
322
323 assert_eq!(format!("{}", eui48), "4D-7E-54-97-2E-EF");
324}
325
326#[test]
327fn test_display_eui64() {
328 extern crate std;
329 use std::format;
330
331 let eui64 = Eui64::from(5583992946972634863);
332
333 assert_eq!(format!("{}", eui64), "4D-7E-54-00-00-97-2E-EF");
334}
335
336#[test]
337fn test_format_upper_hex_eui48() {
338 extern crate std;
339 use std::format;
340
341 let eui48 = Eui48::from(85204980412143);
342
343 assert_eq!(format!("{:X}", eui48), "4D7E54972EEF");
344}
345
346#[test]
347fn test_format_upper_hex_eui64() {
348 extern crate std;
349 use std::format;
350
351 let eui64 = Eui64::from(5583992946972634863);
352
353 assert_eq!(format!("{:X}", eui64), "4D7E540000972EEF");
354}
355
356#[test]
357fn test_format_lower_hex_eui48() {
358 extern crate std;
359 use std::format;
360
361 let eui48 = Eui48::from(85204980412143);
362
363 assert_eq!(format!("{:x}", eui48), "4d7e54972eef");
364}
365
366#[test]
367fn test_format_lower_hex_eui64() {
368 extern crate std;
369 use std::format;
370
371 let eui64 = Eui64::from(5583992946972634863);
372
373 assert_eq!(format!("{:x}", eui64), "4d7e540000972eef");
374}
375
376#[test]
377fn test_eui48_try_from_string() {
378 let eui48 = Eui48::try_from("4D7E54972EEF").unwrap();
379
380 assert_eq!(u64::from(eui48), 85204980412143);
381}
382
383#[test]
384fn test_eui64_try_from_string() {
385 let eui64 = Eui64::try_from("4D7E540000972EEF").unwrap();
386
387 assert_eq!(u64::from(eui64), 5583992946972634863);
388}
389
390#[test]
391fn test_eui48_try_from_string_with_separator() {
392 let eui48_1 = Eui48::try_from("4D-7E-54-97-2E-EF").unwrap();
393 let eui48_2 = Eui48::try_from("4D:7E:54:97:2E:EF").unwrap();
394
395 assert_eq!(u64::from(eui48_1), 85204980412143);
396 assert_eq!(u64::from(eui48_2), 85204980412143);
397}
398
399#[test]
400fn test_eui64_try_from_string_with_separator() {
401 let eui64_1 = Eui64::try_from("4D-7E-54-00-00-97-2E-EF").unwrap();
402 let eui64_2 = Eui64::try_from("4D:7E:54:00:00:97:2E:EF").unwrap();
403
404 assert_eq!(u64::from(eui64_1), 5583992946972634863);
405 assert_eq!(u64::from(eui64_2), 5583992946972634863);
406}
407
408#[test]
409fn test_eui48_try_from_invalid_length() {
410 assert_eq!(
411 Eui48::try_from("").err().unwrap(),
412 StringToEuiError::InvalidLength { length: 0 }
413 );
414
415 assert_eq!(
416 Eui48::try_from("4d7e54972e").err().unwrap(),
417 StringToEuiError::InvalidLength { length: 10 }
418 );
419
420 assert_eq!(
421 Eui48::try_from("4d7e54972eefef4d").err().unwrap(),
422 StringToEuiError::InvalidLength { length: 16 }
423 );
424
425 assert_eq!(
426 Eui48::try_from("4d7e54972eefef4da").err().unwrap(),
427 StringToEuiError::InvalidLength { length: 17 }
428 );
429}
430
431#[test]
432fn test_eui64_try_from_invalid_length() {
433 assert_eq!(
434 Eui64::try_from("").err().unwrap(),
435 StringToEuiError::InvalidLength { length: 0 }
436 );
437
438 assert_eq!(
439 Eui64::try_from("4d7e54972eaa").err().unwrap(),
440 StringToEuiError::InvalidLength { length: 12 }
441 );
442
443 assert_eq!(
444 Eui64::try_from("4d7e54972eefef4ddd").err().unwrap(),
445 StringToEuiError::InvalidLength { length: 18 }
446 );
447}
448
449#[test]
450fn test_eui48_try_from_invalid_character() {
451 assert_eq!(
452 Eui48::try_from("ad7e54972eja").err().unwrap(),
453 StringToEuiError::InvalidChar { char: 'j' }
454 );
455}
456
457#[test]
458fn test_eui64_try_from_invalid_character() {
459 assert_eq!(
460 Eui64::try_from("ad7e54972ea721sa").err().unwrap(),
461 StringToEuiError::InvalidChar { char: 's' }
462 );
463}
464
465#[test]
466fn test_eui48_try_from_invalid_separator_position() {
467 assert_eq!(
468 Eui48::try_from(":4d7e:54:97:2e:ef").err().unwrap(),
469 StringToEuiError::InvalidSeparatorPlace
470 );
471
472 assert_eq!(
473 Eui48::try_from("4d:7e:54:97:2eef:").err().unwrap(),
474 StringToEuiError::InvalidSeparatorPlace
475 );
476
477 assert_eq!(
478 Eui48::try_from("4d::7e54:97:2e:ef").err().unwrap(),
479 StringToEuiError::InvalidSeparatorPlace
480 );
481}
482
483#[test]
484fn test_eui64_try_from_invalid_separator_position() {
485 assert_eq!(
486 Eui64::try_from(":4d7e:54:00:00:97:2e:ef").err().unwrap(),
487 StringToEuiError::InvalidSeparatorPlace
488 );
489
490 assert_eq!(
491 Eui64::try_from("4d:7e:54:00:00:97:2eef:").err().unwrap(),
492 StringToEuiError::InvalidSeparatorPlace
493 );
494
495 assert_eq!(
496 Eui64::try_from("4d::7e54:00:00:97:2e:ef").err().unwrap(),
497 StringToEuiError::InvalidSeparatorPlace
498 );
499}
500
501#[test]
502fn test_eui48_try_from_string_different_separators() {
503 assert_eq!(
504 Eui48::try_from("4d:7e:54-97:2e:ef").err().unwrap(),
505 StringToEuiError::OnlyOneSeparatorTypeExpected
506 );
507}
508
509#[test]
510fn test_eui64_try_from_string_different_separators() {
511 assert_eq!(
512 Eui64::try_from("4d:7e-54:00:00:97:2e-ef").err().unwrap(),
513 StringToEuiError::OnlyOneSeparatorTypeExpected
514 );
515}