eui/
lib.rs

1//! EUI-48 and EUI-64 no-std implementation using heapless.
2//!
3//! # Example
4//!
5//! ```rust
6//! use eui::Eui48;
7//! use eui::Eui64;
8//!
9//! let eui48 = Eui48::from(85204980412143);
10//! let eui64 = Eui64::from(eui48);
11//!     
12//! assert_eq!(eui48.to_string(), "4D-7E-54-97-2E-EF");
13//! assert_eq!(eui64.to_string(), "4D-7E-54-00-00-97-2E-EF");
14//! ```
15#![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/// Possible errors while converting string to eui.
88#[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                // String may contain separator after every second character.
129                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}