irox_tools/util/
uuid.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3//
4
5//!
6//! A basic implementation of a UUID
7//!
8
9use core::fmt::{Display, Formatter};
10
11use irox_bits::{Bits, Error, MutBits};
12
13///
14/// A basic UUID structure.
15#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
16pub struct UUID {
17    inner: u128,
18}
19
20impl From<u128> for UUID {
21    fn from(value: u128) -> Self {
22        UUID { inner: value }
23    }
24}
25
26impl From<&u128> for UUID {
27    fn from(value: &u128) -> Self {
28        UUID { inner: *value }
29    }
30}
31
32impl From<UUID> for u128 {
33    fn from(value: UUID) -> Self {
34        value.inner
35    }
36}
37
38impl From<&UUID> for u128 {
39    fn from(value: &UUID) -> Self {
40        value.inner
41    }
42}
43impl From<UUID> for [u8; 16] {
44    fn from(value: UUID) -> Self {
45        value.inner.to_be_bytes()
46    }
47}
48
49impl From<&UUID> for [u8; 16] {
50    fn from(value: &UUID) -> Self {
51        value.inner.to_be_bytes()
52    }
53}
54
55impl From<[u8; 16]> for UUID {
56    fn from(value: [u8; 16]) -> Self {
57        u128::from_be_bytes(value).into()
58    }
59}
60
61#[derive(Debug, Copy, Clone, Eq, PartialEq)]
62pub enum UUIDParseError {
63    WrongSize,
64    InvalidCharacter,
65}
66
67impl TryFrom<&[u8]> for UUID {
68    type Error = UUIDParseError;
69
70    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
71        if value.len() != 16 {
72            return Err(UUIDParseError::WrongSize);
73        }
74        let mut inner = 0u128;
75        let mut shift = 128;
76        for b in value {
77            shift -= 8;
78            let b = (*b as u128).wrapping_shl(shift);
79            inner |= b;
80        }
81        Ok(UUID { inner })
82    }
83}
84
85impl TryFrom<&str> for UUID {
86    type Error = UUIDParseError;
87
88    fn try_from(value: &str) -> Result<Self, Self::Error> {
89        let no_dashes = value.replace('-', "");
90        if no_dashes.len() != 32 {
91            return Err(UUIDParseError::WrongSize);
92        }
93        let mut inner: u128 = 0;
94        let mut shift = 128;
95        for c in no_dashes.as_bytes() {
96            let val = match *c as char {
97                '0' => 0u8,
98                '1' => 1,
99                '2' => 2,
100                '3' => 3,
101                '4' => 4,
102                '5' => 5,
103                '6' => 6,
104                '7' => 7,
105                '8' => 8,
106                '9' => 9,
107                'A' | 'a' => 10,
108                'B' | 'b' => 11,
109                'C' | 'c' => 12,
110                'D' | 'd' => 13,
111                'E' | 'e' => 14,
112                'F' | 'f' => 15,
113                _ => return Err(UUIDParseError::InvalidCharacter),
114            };
115            shift -= 4;
116            inner |= (val as u128).wrapping_shl(shift);
117        }
118        Ok(UUID { inner })
119    }
120}
121
122///
123/// A trait that can be applied to a Reader, or other bit stream.
124pub trait UUIDReader {
125    ///
126    /// Attempts to read a UUID from this data source, returning the UUID read, or an error if it
127    /// could not.
128    fn read_uuid(&mut self) -> Result<UUID, Error>;
129}
130
131impl<T> UUIDReader for T
132where
133    T: Bits,
134{
135    fn read_uuid(&mut self) -> Result<UUID, Error> {
136        Ok(self.read_be_u128()?.into())
137    }
138}
139
140///
141/// A trait that can be applied to a Writer, or other bit stream.
142pub trait UUIDWriter {
143    ///
144    /// Attempts to write a UUID to this data source
145    fn write_uuid(&mut self, uuid: &UUID) -> Result<(), Error>;
146}
147
148impl<T> UUIDWriter for T
149where
150    T: MutBits,
151{
152    fn write_uuid(&mut self, uuid: &UUID) -> Result<(), Error> {
153        self.write_be_u128(uuid.inner)
154    }
155}
156
157impl Display for UUID {
158    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
159        // 8-4-4-4-12 chars : 4-2-2-2-6 bytes : 32-16-16-16-48 bits
160        let a = (self.inner & 0xFFFFFFFF_0000_0000_0000_000000000000) >> 96;
161        let b = (self.inner & 0x00000000_FFFF_0000_0000_000000000000) >> 80;
162        let c = (self.inner & 0x00000000_0000_FFFF_0000_000000000000) >> 64;
163        let d = (self.inner & 0x00000000_0000_0000_FFFF_000000000000) >> 48;
164        let e = self.inner & 0x00000000_0000_0000_0000_FFFFFFFFFFFF;
165        f.write_fmt(format_args!("{a:08X}-{b:04X}-{c:04X}-{d:04X}-{e:012X}"))
166    }
167}
168
169impl UUID {
170    ///
171    /// Generates a new random UUID
172    #[must_use]
173    pub fn new_random() -> UUID {
174        use crate::random::PRNG;
175        let mut random = crate::random::Random::default();
176        UUID {
177            inner: random.next_u128(),
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use crate::uuid::{UUIDParseError, UUID};
185
186    #[test]
187    pub fn display_test() {
188        let uuid = UUID { inner: 0 };
189        assert_eq!("00000000-0000-0000-0000-000000000000", format!("{uuid}"));
190
191        let uuid = UUID { inner: u128::MAX };
192        assert_eq!("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", format!("{uuid}"));
193    }
194
195    #[test]
196    pub fn parse_test() -> Result<(), UUIDParseError> {
197        let uuid = UUID::new_random();
198        let disp = format!("{uuid}");
199
200        let parsed: UUID = disp.as_str().try_into()?;
201        assert_eq!(parsed, uuid);
202
203        let parsed: u128 = parsed.into();
204        let parsed: UUID = (parsed.to_be_bytes().as_slice()).try_into()?;
205        assert_eq!(parsed, uuid);
206
207        Ok(())
208    }
209}