smb_dtyp/
guid.rs

1use std::{fmt::Display, io::Cursor, str::FromStr};
2
3use binrw::prelude::*;
4use rand::{Rng, rngs::OsRng};
5
6/// Represents a standard, 16-byte GUID.
7///
8/// Supports [`std::mem::size_of`].
9#[derive(BinRead, BinWrite, Clone, Copy, PartialEq, Eq, Default)]
10#[brw(little)]
11pub struct Guid(u32, u16, u16, [u8; 8]);
12
13impl Guid {
14    /// The size of a GUID, in Bytes
15    pub const GUID_SIZE: usize = 16;
16    const _VALIDATE_SIZE_OF: [u8; Self::GUID_SIZE] = [0; size_of::<Self>()];
17
18    pub const ZERO: Guid = Guid(0, 0, 0, [0; 8]);
19
20    /// Generates a new random GUID.
21    pub fn generate() -> Self {
22        let mut rng = OsRng;
23        let mut bytes = [0u8; 16];
24        rng.fill(&mut bytes);
25        Self::try_from(&bytes).unwrap()
26    }
27
28    /// The maximum possible GUID value (all bits set to 1).
29    pub const MAX: Guid = Guid(u32::MAX, u16::MAX, u16::MAX, [u8::MAX; 8]);
30
31    pub const fn parse_uuid(s: &str) -> Result<Guid, &'static str> {
32        use super::util::parse_byte;
33        let b = s.as_bytes();
34        let so = if b[0] == b'{' && b[b.len() - 1] == b'}' {
35            if s.len() != 38 {
36                return Err("Invalid UUID format");
37            }
38            1
39        } else {
40            if s.len() != 36 {
41                return Err("Invalid UUID format");
42            }
43            0
44        };
45        if b[so + 8] != b'-' || b[so + 13] != b'-' || b[so + 18] != b'-' || b[so + 23] != b'-' {
46            return Err("Invalid UUID format");
47        }
48
49        /// A macro to perform the same as `parse_bytes(b, i)?`,
50        /// which is impossible in a const context.
51        macro_rules! parse_byte {
52            ($b:expr, $i:expr) => {
53                match parse_byte($b, $i) {
54                    Ok(val) => val,
55                    Err(e) => return Err(e),
56                }
57            };
58        }
59
60        Ok(Guid(
61            u32::from_be_bytes([
62                parse_byte!(b, so),
63                parse_byte!(b, so + 2),
64                parse_byte!(b, so + 4),
65                parse_byte!(b, so + 6),
66            ]),
67            u16::from_be_bytes([parse_byte!(b, so + 9), parse_byte!(b, so + 11)]),
68            u16::from_be_bytes([parse_byte!(b, so + 14), parse_byte!(b, so + 16)]),
69            [
70                parse_byte!(b, so + 19),
71                parse_byte!(b, so + 21),
72                parse_byte!(b, so + 24),
73                parse_byte!(b, so + 26),
74                parse_byte!(b, so + 28),
75                parse_byte!(b, so + 30),
76                parse_byte!(b, so + 32),
77                parse_byte!(b, so + 34),
78            ],
79        ))
80    }
81}
82
83/// A macro to create a `Guid` from a string literal at compile time.
84///
85/// Prefer the [`make_guid!`] alias.
86///
87/// ```
88/// use smb_dtyp::make_guid;
89/// let guid = make_guid!("065eadf1-6daf-1543-b04f-10e69084c9ae");
90/// assert_eq!(guid.to_string(), "065eadf1-6daf-1543-b04f-10e69084c9ae");
91/// ```
92#[macro_export]
93macro_rules! guid {
94    ($s:literal) => {{
95        match $crate::Guid::parse_uuid($s) {
96            Ok(guid) => guid,
97            Err(_) => panic!("Invalid GUID format"),
98        }
99    }};
100}
101
102/// Alias for [`guid!`] following a verb–noun naming convention, used by `smb-fscc` for filesystem-info GUIDs.
103/// Prefer `make_guid!` when constructing GUIDs in SMB filesystem contexts.
104pub use guid as make_guid;
105
106impl From<[u8; 16]> for Guid {
107    fn from(value: [u8; 16]) -> Self {
108        Self::try_from(&value).unwrap()
109    }
110}
111
112impl TryFrom<&[u8; 16]> for Guid {
113    type Error = binrw::Error;
114
115    fn try_from(value: &[u8; 16]) -> Result<Self, Self::Error> {
116        let mut cursor = Cursor::new(value);
117        Guid::read(&mut cursor)
118    }
119}
120
121impl From<Guid> for [u8; 16] {
122    fn from(val: Guid) -> Self {
123        let mut cursor = Cursor::new(Vec::new());
124        val.write(&mut cursor).unwrap();
125        cursor.into_inner().try_into().unwrap()
126    }
127}
128
129impl FromStr for Guid {
130    type Err = &'static str;
131
132    fn from_str(s: &str) -> Result<Self, Self::Err> {
133        Guid::parse_uuid(s)
134    }
135}
136
137impl Display for Guid {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        // Print first fields in little endian, and the rest in big endian:
140        write!(
141            f,
142            "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:12x}",
143            self.0,
144            self.1,
145            self.2,
146            self.3[0],
147            self.3[1],
148            self.3[2..]
149                .iter()
150                .fold(0u64, |acc, &x| (acc << 8) + x as u64)
151        )
152    }
153}
154
155impl std::fmt::Debug for Guid {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(f, "{self}")
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    const TEST_GUID_STR: &str = "065eadf1-6daf-1543-b04f-10e69084c9ae";
166    const PARSED_GUID_VALUE: Guid = Guid(
167        0x065eadf1,
168        0x6daf,
169        0x1543,
170        [0xb0, 0x4f, 0x10, 0xe6, 0x90, 0x84, 0xc9, 0xae],
171    );
172    const TEST_GUID_BYTES: [u8; 16] = [
173        0xf1u8, 0xad, 0x5e, 0x06, 0xaf, 0x6d, 0x43, 0x15, 0xb0, 0x4f, 0x10, 0xe6, 0x90, 0x84, 0xc9,
174        0xae,
175    ];
176
177    #[test]
178    pub fn test_guid_parse_runtime() {
179        let guid = TEST_GUID_STR.parse::<Guid>().unwrap();
180        assert_eq!(guid, PARSED_GUID_VALUE);
181        assert_eq!(guid.to_string(), TEST_GUID_STR);
182    }
183
184    #[test]
185    pub fn test_const_guid() {
186        assert_eq!(
187            make_guid!("065eadf1-6daf-1543-b04f-10e69084c9ae"),
188            PARSED_GUID_VALUE
189        );
190        assert_eq!(
191            make_guid!("{065eadf1-6daf-1543-b04f-10e69084c9ae}"),
192            PARSED_GUID_VALUE
193        );
194    }
195
196    #[test]
197    pub fn test_guid_parse_bytes() {
198        assert_eq!(Guid::try_from(&TEST_GUID_BYTES).unwrap(), PARSED_GUID_VALUE);
199    }
200
201    #[test]
202    pub fn test_guid_write_bytes() {
203        let mut cursor = Cursor::new(Vec::new());
204        PARSED_GUID_VALUE.write(&mut cursor).unwrap();
205        assert_eq!(cursor.into_inner(), TEST_GUID_BYTES);
206    }
207}