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