Skip to main content

wslplugins_rs/user_distribution_id/
serde_impl.rs

1//! Serde support for [`crate::UserDistributionID`].
2//!
3//! Human-readable serializers use the canonical GUID string representation.
4//! Compact serializers use the native 16-byte Windows GUID memory layout rather
5//! than RFC 4122 byte order so the encoded bytes match Windows API expectations.
6
7use std::{ptr, slice};
8use windows_core::GUID;
9
10use crate::UserDistributionID;
11
12#[inline]
13const fn guid_to_windows_bytes(guid: &windows_core::GUID) -> &[u8] {
14    // SAFETY: The layout of windows_core::GUID is guaranteed to be 16 bytes and match the Windows GUID layout
15    unsafe { slice::from_raw_parts(std::ptr::from_ref::<GUID>(guid).cast::<u8>(), 16) }
16}
17
18#[inline]
19const fn guid_from_windows_bytes(bytes: &[u8; 16]) -> GUID {
20    // SAFETY: We know guid is 16 bytes and the caller guarantees the bytes are a valid Windows [GUID] representation
21    unsafe { ptr::read_unaligned(bytes.as_ptr().cast::<GUID>()) }
22}
23
24impl serde::Serialize for UserDistributionID {
25    #[inline]
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: serde::Serializer,
29    {
30        if serializer.is_human_readable() {
31            serializer.collect_str(self)
32        } else {
33            serializer.serialize_bytes(guid_to_windows_bytes(&self.0))
34        }
35    }
36}
37
38impl<'de> serde::Deserialize<'de> for UserDistributionID {
39    #[inline]
40    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41    where
42        D: serde::Deserializer<'de>,
43    {
44        struct UserDistributionIDVisitor;
45
46        impl UserDistributionIDVisitor {
47            fn from_str<E>(value: &str) -> Result<UserDistributionID, E>
48            where
49                E: serde::de::Error,
50            {
51                value.parse::<UserDistributionID>().map_err(E::custom)
52            }
53
54            fn from_bytes<E>(bytes: &[u8]) -> Result<UserDistributionID, E>
55            where
56                E: serde::de::Error,
57            {
58                if bytes.len() != 16 {
59                    return Err(E::invalid_length(bytes.len(), &"a 16-byte GUID"));
60                }
61
62                let bytes: [u8; 16] = bytes
63                    .try_into()
64                    .map_err(|_| E::custom("invalid GUID format in byte array"))?;
65                Ok(UserDistributionID(guid_from_windows_bytes(&bytes)))
66            }
67
68            fn from_seq<'de, A>(mut seq: A) -> Result<UserDistributionID, A::Error>
69            where
70                A: serde::de::SeqAccess<'de>,
71            {
72                let mut bytes = [0u8; 16];
73                for (index, slot) in bytes.iter_mut().enumerate() {
74                    *slot = seq.next_element()?.ok_or_else(|| {
75                        serde::de::Error::invalid_length(index, &"a 16-byte GUID")
76                    })?;
77                }
78
79                if seq.next_element::<u8>()?.is_some() {
80                    return Err(serde::de::Error::invalid_length(17, &"a 16-byte GUID"));
81                }
82
83                Ok(UserDistributionID(guid_from_windows_bytes(&bytes)))
84            }
85        }
86
87        impl<'de> serde::de::Visitor<'de> for UserDistributionIDVisitor {
88            type Value = UserDistributionID;
89
90            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91                formatter.write_str("a GUID string or 16-byte GUID")
92            }
93
94            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
95            where
96                E: serde::de::Error,
97            {
98                Self::from_str(value)
99            }
100
101            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
102            where
103                E: serde::de::Error,
104            {
105                Self::from_bytes(value)
106            }
107
108            fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
109            where
110                A: serde::de::SeqAccess<'de>,
111            {
112                Self::from_seq(seq)
113            }
114        }
115
116        if deserializer.is_human_readable() {
117            deserializer.deserialize_str(UserDistributionIDVisitor)
118        } else {
119            deserializer.deserialize_bytes(UserDistributionIDVisitor)
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use crate::UserDistributionID;
127    use serde_test::{assert_tokens, Configure, Token};
128    use std::str::FromStr;
129    use windows_core::GUID;
130
131    #[test]
132    fn serde_roundtrip_uses_guid_string() {
133        let value = UserDistributionID(GUID::from_u128(0x12345678_9abc_def0_1357_2468ace0bdf1));
134        assert_tokens(
135            &value.readable(),
136            &[Token::Str("12345678-9ABC-DEF0-1357-2468ACE0BDF1")],
137        );
138    }
139
140    #[test]
141    fn serde_compact_uses_windows_guid_bytes() {
142        let value = UserDistributionID(GUID::from_u128(0x12345678_9abc_def0_1357_2468ace0bdf1));
143
144        assert_tokens(
145            &value.compact(),
146            &[Token::BorrowedBytes(&[
147                0x78, 0x56, 0x34, 0x12, 0xbc, 0x9a, 0xf0, 0xde, 0x13, 0x57, 0x24, 0x68, 0xac, 0xe0,
148                0xbd, 0xf1,
149            ])],
150        );
151    }
152
153    #[test]
154    fn binary_layout_matches_windows_guid_layout() {
155        let guid = GUID::from_u128(0x12345678_9abc_def0_1357_2468ace0bdf1);
156        assert_eq!(
157            super::guid_to_windows_bytes(&guid),
158            [
159                0x78, 0x56, 0x34, 0x12, 0xbc, 0x9a, 0xf0, 0xde, 0x13, 0x57, 0x24, 0x68, 0xac, 0xe0,
160                0xbd, 0xf1,
161            ]
162        );
163    }
164
165    #[test]
166    fn binary_deserialization_uses_windows_guid_layout() {
167        let bytes = [
168            0x78, 0x56, 0x34, 0x12, 0xbc, 0x9a, 0xf0, 0xde, 0x13, 0x57, 0x24, 0x68, 0xac, 0xe0,
169            0xbd, 0xf1,
170        ];
171        let guid = super::guid_from_windows_bytes(&bytes);
172        assert_eq!(
173            guid,
174            GUID::from_u128(0x12345678_9abc_def0_1357_2468ace0bdf1)
175        );
176    }
177
178    #[test]
179    fn binary_layout_matches_expected_bytes_for_known_guid() {
180        #[allow(clippy::unwrap_used)]
181        let id = UserDistributionID::from_str("80E4258D-0E16-4301-B8BE-E7833D02A7AA").unwrap();
182
183        assert_eq!(
184            super::guid_to_windows_bytes(&id.0),
185            [141, 37, 228, 128, 22, 14, 1, 67, 184, 190, 231, 131, 61, 2, 167, 170,]
186        );
187    }
188}