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