uhlc/
id.rs

1//
2// Copyright (c) 2017, 2020 ADLINK Technology Inc.
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11use alloc::string::{String, ToString};
12use core::{
13    convert::{TryFrom, TryInto},
14    fmt,
15    hash::Hash,
16    num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8},
17    str::FromStr,
18};
19
20use rand::Rng;
21use serde::{Deserialize, Serialize};
22
23/// An identifier for an HLC ([MAX_SIZE](ID::MAX_SIZE) bytes maximum).
24/// This struct has a constant memory size (holding internally a `NonZeroU8`),
25/// allowing allocations on the stack for better performances.
26///
27/// # Examples
28///
29/// ```
30/// use std::convert::TryFrom;
31/// use uhlc::ID;
32///
33/// let buf = [0x1a, 0x2b, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00,
34///            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
35/// // NOTE: ID::try_from(slice: &[u8]) assumes the slice is in little endian
36/// let id1 = ID::try_from(&buf[..3]).unwrap();
37/// assert_eq!(id1.size(), 3);
38/// assert_eq!(id1.to_le_bytes(), buf);
39/// assert_eq!(&id1.to_le_bytes()[..id1.size()], &[0x1a, 0x2b, 0x3c]);
40/// let id2: ID = "3c2b1a".parse().unwrap();
41/// assert_eq!(id2.size(), 3);
42/// assert_eq!(id2.to_le_bytes(), buf);
43/// assert_eq!(&id2.to_le_bytes()[..id2.size()], &[0x1a, 0x2b, 0x3c]);
44/// assert_eq!(id2.to_string(), "3c2b1a");
45/// assert_eq!(id1, id2);
46/// ```
47///
48/// ```
49/// use uhlc::ID;
50///
51/// let id = ID::rand();
52/// assert!(id.size() <= 16);
53/// ```
54#[derive(Copy, Clone, Eq, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Hash)]
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56#[repr(transparent)]
57pub struct ID([u8; ID::MAX_SIZE]);
58
59impl ID {
60    /// The maximum size of an le-encoded [`ID`](`ID`) in bytes: 16.
61    pub const MAX_SIZE: usize = u128::BITS as usize / 8;
62
63    /// The size of this [`ID`](`ID`) in bytes. I.e., the number of significant bytes of the le-encoded [`ID`](`ID`).
64    #[inline]
65    pub fn size(&self) -> usize {
66        Self::MAX_SIZE - (u128::from_le_bytes(self.0).leading_zeros() as usize / 8)
67    }
68
69    /// This ID as bytes
70    ///
71    /// If you want to retrive a le-encoded slice of the [`ID`](`ID`), you can do it as follows:
72    /// ```
73    /// use uhlc::ID;
74    /// use std::convert::TryFrom;
75    ///
76    /// let id = ID::try_from(&[0x01]).unwrap();
77    /// let slice = &id.to_le_bytes()[..id.size()];
78    /// assert_eq!(1, slice.len());
79    /// assert_eq!(&[0x01], slice);
80    /// ```
81    #[inline]
82    pub fn to_le_bytes(&self) -> [u8; Self::MAX_SIZE] {
83        self.0
84    }
85
86    /// Generate a random [`ID`](`ID`).
87    #[inline]
88    pub fn rand() -> Self {
89        use rand::rngs::OsRng;
90        let id: u128 = OsRng.gen_range(1..u128::MAX);
91        Self(id.to_le_bytes())
92    }
93}
94
95#[derive(Debug, Clone, Copy)]
96#[cfg_attr(feature = "defmt", derive(defmt::Format))]
97pub struct SizeError(pub usize);
98impl fmt::Display for SizeError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(
101            f,
102            "Maximum ID size ({} bytes) exceeded: {}",
103            ID::MAX_SIZE,
104            self.0
105        )
106    }
107}
108
109#[cfg(feature = "std")]
110impl std::error::Error for SizeError {}
111
112macro_rules! impl_from_sized_slice_for_id {
113    ($N: expr) => {
114        impl TryFrom<&[u8; $N]> for ID {
115            type Error = SizeError;
116
117            /// Performs the conversion.
118            /// NOTE: the bytes slice is interpreted as little endian
119            fn try_from(value: &[u8; $N]) -> Result<Self, Self::Error> {
120                let mut id = [0u8; ID::MAX_SIZE];
121                id[..$N].copy_from_slice(value);
122                let id = u128::from_le_bytes(id);
123                match NonZeroU128::new(id) {
124                    Some(_) => Ok(Self(id.to_le_bytes())),
125                    None => Err(SizeError(0)),
126                }
127            }
128        }
129
130        impl TryFrom<[u8; $N]> for ID {
131            type Error = SizeError;
132
133            /// Performs the conversion.
134            /// NOTE: the bytes slice is interpreted as little endian
135            fn try_from(id: [u8; $N]) -> Result<Self, Self::Error> {
136                (&id).try_into()
137            }
138        }
139    };
140}
141impl_from_sized_slice_for_id!(1);
142impl_from_sized_slice_for_id!(2);
143impl_from_sized_slice_for_id!(3);
144impl_from_sized_slice_for_id!(4);
145impl_from_sized_slice_for_id!(5);
146impl_from_sized_slice_for_id!(6);
147impl_from_sized_slice_for_id!(7);
148impl_from_sized_slice_for_id!(8);
149impl_from_sized_slice_for_id!(9);
150impl_from_sized_slice_for_id!(10);
151impl_from_sized_slice_for_id!(11);
152impl_from_sized_slice_for_id!(12);
153impl_from_sized_slice_for_id!(13);
154impl_from_sized_slice_for_id!(14);
155impl_from_sized_slice_for_id!(15);
156impl_from_sized_slice_for_id!(16);
157
158impl TryFrom<&[u8]> for ID {
159    type Error = SizeError;
160
161    /// Performs the conversion.  
162    /// NOTE: the bytes slice is interpreted as little endian
163    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
164        let size = slice.len();
165        if size > Self::MAX_SIZE {
166            return Err(SizeError(size));
167        }
168        let mut id = [0u8; ID::MAX_SIZE];
169        id[..size].copy_from_slice(slice);
170        let id = u128::from_le_bytes(id);
171        match NonZeroU128::new(id) {
172            Some(_) => Ok(Self(id.to_le_bytes())),
173            None => Err(SizeError(0)),
174        }
175    }
176}
177
178impl TryFrom<u8> for ID {
179    type Error = SizeError;
180
181    fn try_from(id: u8) -> Result<Self, Self::Error> {
182        id.to_le_bytes().try_into()
183    }
184}
185
186impl From<NonZeroU8> for ID {
187    fn from(id: NonZeroU8) -> Self {
188        NonZeroU128::from(id).into()
189    }
190}
191
192impl TryFrom<u16> for ID {
193    type Error = SizeError;
194
195    fn try_from(id: u16) -> Result<Self, Self::Error> {
196        id.to_le_bytes().try_into()
197    }
198}
199
200impl From<NonZeroU16> for ID {
201    fn from(id: NonZeroU16) -> Self {
202        NonZeroU128::from(id).into()
203    }
204}
205
206impl TryFrom<u32> for ID {
207    type Error = SizeError;
208
209    fn try_from(id: u32) -> Result<Self, Self::Error> {
210        id.to_le_bytes().try_into()
211    }
212}
213
214impl From<NonZeroU32> for ID {
215    fn from(id: NonZeroU32) -> Self {
216        NonZeroU128::from(id).into()
217    }
218}
219
220impl TryFrom<u64> for ID {
221    type Error = SizeError;
222
223    fn try_from(id: u64) -> Result<Self, Self::Error> {
224        id.to_le_bytes().try_into()
225    }
226}
227
228impl From<NonZeroU64> for ID {
229    fn from(id: NonZeroU64) -> Self {
230        NonZeroU128::from(id).into()
231    }
232}
233
234impl TryFrom<u128> for ID {
235    type Error = SizeError;
236
237    fn try_from(id: u128) -> Result<Self, Self::Error> {
238        id.to_le_bytes().try_into()
239    }
240}
241
242impl From<NonZeroU128> for ID {
243    fn from(id: NonZeroU128) -> Self {
244        Self(id.get().to_le_bytes())
245    }
246}
247
248impl FromStr for ID {
249    type Err = ParseIDError;
250
251    fn from_str(s: &str) -> Result<Self, Self::Err> {
252        if s.is_empty() {
253            return Err(ParseIDError {
254                cause: "Empty strings are not valid".to_string(),
255            });
256        }
257
258        if s.starts_with('0') {
259            return Err(ParseIDError {
260                cause: "Leading 0s are not valid".to_string(),
261            });
262        }
263
264        let bs = u128::from_str_radix(s, 16).map_err(|e| ParseIDError {
265            cause: e.to_string(),
266        })?;
267        ID::try_from(bs).map_err(|e| ParseIDError {
268            cause: e.to_string(),
269        })
270    }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
274#[cfg_attr(feature = "defmt", derive(defmt::Format))]
275pub struct ParseIDError {
276    pub cause: String,
277}
278
279impl fmt::Debug for ID {
280    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281        write!(f, "{:x}", u128::from_le_bytes(self.0))
282    }
283}
284
285impl fmt::Display for ID {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        fmt::Debug::fmt(self, f)
288    }
289}
290
291mod tests {
292    #[test]
293    fn parse_display() {
294        let id = "1".parse::<crate::ID>().unwrap();
295        assert_eq!(id.to_string(), "1");
296
297        let id = "1bc0".parse::<crate::ID>().unwrap();
298        assert_eq!(id.to_string(), "1bc0");
299
300        let id = "ff00".parse::<crate::ID>().unwrap();
301        assert_eq!(id.to_string(), "ff00");
302
303        let id = "ff0".parse::<crate::ID>().unwrap();
304        assert_eq!(id.to_string(), "ff0");
305
306        let id = "abcd".parse::<crate::ID>().unwrap();
307        assert_eq!(id.to_string(), "abcd");
308
309        let id = "6bd9cb5f9f2644508fbbb0df1d6cce3a"
310            .parse::<crate::ID>()
311            .unwrap();
312        assert_eq!(id.to_string(), "6bd9cb5f9f2644508fbbb0df1d6cce3a");
313
314        "0".parse::<crate::ID>().unwrap_err();
315        "0bcd".parse::<crate::ID>().unwrap_err();
316        "6bd9cb5f9f2644508fbbb0df1d6cce3a0"
317            .parse::<crate::ID>()
318            .unwrap_err();
319    }
320}