Skip to main content

vhdx/
types.rs

1use std::fmt;
2
3/// A CRC-32C (Castagnoli) checksum value.
4///
5/// Wraps a raw `u32` and displays as `0x`-prefixed hex (e.g. `0xe3069283`).
6#[derive(Clone, Copy, PartialEq, Eq)]
7pub struct Crc32c(u32);
8
9impl Crc32c {
10    /// Create a `Crc32c` from a raw `u32` checksum value.
11    #[must_use]
12    pub const fn from_raw(value: u32) -> Self {
13        Self(value)
14    }
15
16    /// Return the raw `u32` checksum value.
17    #[must_use]
18    pub const fn value(&self) -> u32 {
19        self.0
20    }
21}
22
23impl fmt::Debug for Crc32c {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "Crc32c({:#010x})", self.0)
26    }
27}
28
29impl fmt::Display for Crc32c {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{:#010x}", self.0)
32    }
33}
34
35/// A GUID stored as raw 16 bytes (RFC 4122 / mixed-endian layout as on disk).
36///
37/// Internally uses `uuid::Uuid` for display and parsing, but stores `[u8; 16]`
38/// for zero-copy compatibility with disk structures.
39#[derive(Clone, Copy, PartialEq, Eq, Hash)]
40pub struct Guid {
41    bytes: [u8; 16],
42}
43
44impl Guid {
45    /// Create a `Guid` from raw mixed-endian bytes as stored on disk.
46    #[must_use]
47    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
48        Self { bytes }
49    }
50
51    /// Create a zero GUID (all bytes zero).
52    #[must_use]
53    pub const fn zero() -> Self {
54        Self { bytes: [0u8; 16] }
55    }
56
57    /// Return the raw 16 bytes.
58    #[must_use]
59    pub const fn to_bytes(&self) -> [u8; 16] {
60        self.bytes
61    }
62
63    /// Generate a new random (v4) GUID.
64    #[must_use]
65    pub fn new_v4() -> Self {
66        let uuid = uuid::Uuid::new_v4();
67        Self {
68            bytes: *uuid.as_bytes(),
69        }
70    }
71
72    /// Convert to the underlying `uuid::Uuid`.
73    #[must_use]
74    pub fn to_uuid(&self) -> uuid::Uuid {
75        uuid::Uuid::from_bytes(self.bytes)
76    }
77
78    /// Parse a hyphenated GUID string, with or without surrounding braces.
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the input is not a valid GUID string.
83    pub fn parse_braced(input: &str) -> std::result::Result<Self, uuid::Error> {
84        let trimmed = input.trim();
85        let guid = trimmed
86            .strip_prefix('{')
87            .and_then(|value| value.strip_suffix('}'))
88            .unwrap_or(trimmed);
89        uuid::Uuid::parse_str(guid).map(|uuid| Self {
90            bytes: *uuid.as_bytes(),
91        })
92    }
93}
94
95impl fmt::Debug for Guid {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "Guid({})", self.to_uuid().hyphenated())
98    }
99}
100
101impl fmt::Display for Guid {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.to_uuid().hyphenated())
104    }
105}
106
107/// Standard metadata item GUID constants per MS-VHDX specification.
108#[allow(non_snake_case)]
109pub mod StandardItems {
110    use crate::constants;
111    use crate::types::Guid;
112
113    pub const FILE_PARAMETERS: Guid = constants::FILE_PARAMETERS_GUID;
114    pub const VIRTUAL_DISK_SIZE: Guid = constants::VIRTUAL_DISK_SIZE_GUID;
115    pub const VIRTUAL_DISK_ID: Guid = constants::VIRTUAL_DISK_ID_GUID;
116    pub const LOGICAL_SECTOR_SIZE: Guid = constants::LOGICAL_SECTOR_SIZE_GUID;
117    pub const PHYSICAL_SECTOR_SIZE: Guid = constants::PHYSICAL_SECTOR_SIZE_GUID;
118    pub const PARENT_LOCATOR: Guid = constants::PARENT_LOCATOR_GUID;
119    pub const LOCATOR_TYPE_VHDX: Guid = constants::LOCATOR_TYPE_VHDX_GUID;
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn guid_from_bytes_roundtrip() {
128        let bytes = [
129            0x37, 0x67, 0xA1, 0xCA, 0x36, 0xFA, 0x43, 0x4D, 0xB3, 0xB6, 0x33, 0xF0, 0xAA, 0x44,
130            0xE7, 0x6B,
131        ];
132        let guid = Guid::from_bytes(bytes);
133        assert_eq!(guid.to_bytes(), bytes);
134    }
135
136    #[test]
137    fn guid_display() {
138        let guid = StandardItems::FILE_PARAMETERS;
139        // from_bytes uses raw bytes directly with uuid crate
140        let displayed = format!("{guid}");
141        assert_eq!(displayed, "3767a1ca-36fa-434d-b3b6-33f0aa44e76b");
142    }
143
144    #[test]
145    fn guid_debug() {
146        let guid = StandardItems::FILE_PARAMETERS;
147        let debugged = format!("{guid:?}");
148        assert!(debugged.starts_with("Guid("));
149        assert!(debugged.ends_with(')'));
150    }
151
152    #[test]
153    fn guid_new_v4_is_unique() {
154        let a = Guid::new_v4();
155        let b = Guid::new_v4();
156        assert_ne!(a, b);
157    }
158
159    #[test]
160    fn standard_items_are_distinct() {
161        let guids = [
162            StandardItems::FILE_PARAMETERS,
163            StandardItems::VIRTUAL_DISK_SIZE,
164            StandardItems::VIRTUAL_DISK_ID,
165            StandardItems::LOGICAL_SECTOR_SIZE,
166            StandardItems::PHYSICAL_SECTOR_SIZE,
167            StandardItems::PARENT_LOCATOR,
168            StandardItems::LOCATOR_TYPE_VHDX,
169        ];
170        for (i, a) in guids.iter().enumerate() {
171            for (j, b) in guids.iter().enumerate() {
172                if i != j {
173                    assert_ne!(a, b, "StandardItems at index {i} and {j} should differ");
174                }
175            }
176        }
177    }
178}