ps_uuid/methods/
new_dcom.rs1#![allow(clippy::cast_possible_truncation)]
2use std::{
3 ops::BitXor,
4 time::{SystemTime, UNIX_EPOCH},
5};
6
7use crate::{UuidConstructionError, UUID};
8
9impl UUID {
10 pub fn new_dcom(
22 timestamp: SystemTime,
23 node_id: [u8; 6],
24 ) -> Result<Self, UuidConstructionError> {
25 const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
27
28 let duration_since_unix = timestamp
29 .duration_since(UNIX_EPOCH)
30 .map_err(|_| UuidConstructionError::TimestampBeforeEpoch)?;
31
32 let filetime = u64::try_from(duration_since_unix.as_nanos() / 100)
33 .map_err(|_| UuidConstructionError::TimestampOverflow)?
34 .checked_add(FILETIME_EPOCH_OFFSET)
35 .ok_or(UuidConstructionError::TimestampOverflow)?;
36
37 let time_low = (filetime & 0xFFFF_FFFF) as u32;
39 let time_mid = ((filetime >> 32) & 0xFFFF) as u16;
40 let time_hi_and_version = ((filetime >> 48) & 0xFFFF) as u16;
41
42 let clock_seq = ((filetime & 0xFFFF) as u16)
44 .wrapping_mul(0x1234)
45 .bitxor(((filetime >> 16) & 0xFFFF) as u16);
46
47 Ok(Self::from_parts_dcom(
48 time_low,
49 time_mid,
50 time_hi_and_version,
51 clock_seq,
52 node_id,
53 ))
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 #![allow(clippy::expect_used)]
60 use std::{
61 ops::BitXor,
62 time::{Duration, UNIX_EPOCH},
63 };
64
65 use crate::{UuidConstructionError, Variant, UUID};
66
67 const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
68
69 const fn sample_node_id() -> [u8; 6] {
70 [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB]
71 }
72
73 #[test]
74 fn test_new_dcom_basic_functionality() {
75 let timestamp = UNIX_EPOCH + Duration::from_secs(1_000_000);
76 let node_id = sample_node_id();
77
78 let result = UUID::new_dcom(timestamp, node_id);
79 assert!(result.is_ok());
80
81 let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
82 assert_eq!(uuid.get_variant(), Variant::DCOM);
83 }
84
85 #[test]
86 fn test_new_dcom_unix_epoch() {
87 let timestamp = UNIX_EPOCH;
88 let node_id = sample_node_id();
89
90 let result = UUID::new_dcom(timestamp, node_id);
91 assert!(result.is_ok());
92
93 let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
94 assert_eq!(uuid.get_variant(), Variant::DCOM);
95 }
96
97 #[test]
98 fn test_new_dcom_before_unix_epoch() {
99 let timestamp = UNIX_EPOCH - Duration::from_secs(1);
100 let node_id = sample_node_id();
101
102 let result = UUID::new_dcom(timestamp, node_id);
103 assert!(matches!(
104 result,
105 Err(UuidConstructionError::TimestampBeforeEpoch)
106 ));
107 }
108
109 #[test]
110 fn test_new_dcom_filetime_calculation() {
111 let timestamp = UNIX_EPOCH + Duration::from_secs(1);
112 let node_id = sample_node_id();
113
114 let uuid = UUID::new_dcom(timestamp, node_id)
115 .expect("new_dcom should succeed for valid timestamp and node id");
116
117 let expected_filetime = 10_000_000 + FILETIME_EPOCH_OFFSET;
119
120 let time_low =
122 u32::from_le_bytes([uuid.bytes[0], uuid.bytes[1], uuid.bytes[2], uuid.bytes[3]]);
123 let time_mid = u16::from_le_bytes([uuid.bytes[4], uuid.bytes[5]]);
124 let time_hi = u16::from_le_bytes([uuid.bytes[6], uuid.bytes[7]]);
125
126 let reconstructed_filetime =
127 u64::from(time_low) | (u64::from(time_mid) << 32) | (u64::from(time_hi) << 48);
128
129 assert_eq!(reconstructed_filetime, expected_filetime);
130 }
131
132 #[test]
133 fn test_new_dcom_clock_sequence_algorithm() {
134 let timestamp = UNIX_EPOCH + Duration::from_secs(12345);
135 let node_id = sample_node_id();
136
137 let uuid = UUID::new_dcom(timestamp, node_id)
138 .expect("new_dcom should succeed for valid timestamp and node id");
139
140 let filetime = 12345 * 10_000_000 + FILETIME_EPOCH_OFFSET;
142 let expected_clock_seq = ((filetime & 0xFFFF) as u16)
143 .wrapping_mul(0x1234)
144 .bitxor(((filetime >> 16) & 0xFFFF) as u16);
145
146 let actual_clock_seq = u16::from_be_bytes([uuid.bytes[8], uuid.bytes[9]]);
148
149 let actual_clock_seq_masked = actual_clock_seq & 0x3FFF;
151 let expected_clock_seq_masked = expected_clock_seq & 0x3FFF;
152
153 assert_eq!(actual_clock_seq_masked, expected_clock_seq_masked);
154 }
155
156 #[test]
157 fn test_new_dcom_deterministic() {
158 let timestamp = UNIX_EPOCH + Duration::from_millis(123_456_789);
159 let node_id = sample_node_id();
160
161 let uuid1 = UUID::new_dcom(timestamp, node_id)
162 .expect("new_dcom should succeed for valid timestamp and node id");
163 let uuid2 = UUID::new_dcom(timestamp, node_id)
164 .expect("new_dcom should succeed for valid timestamp and node id");
165
166 assert_eq!(uuid1, uuid2);
167 }
168
169 #[test]
170 fn test_new_dcom_different_timestamps() {
171 let node_id = sample_node_id();
172
173 let uuid1 = UUID::new_dcom(UNIX_EPOCH + Duration::from_secs(1), node_id)
174 .expect("new_dcom should succeed for valid timestamp and node id");
175 let uuid2 = UUID::new_dcom(UNIX_EPOCH + Duration::from_secs(2), node_id)
176 .expect("new_dcom should succeed for valid timestamp and node id");
177
178 assert_ne!(uuid1, uuid2);
179 }
180
181 #[test]
182 fn test_new_dcom_different_node_ids() {
183 let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
184 let node_id1 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
185 let node_id2 = [0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
186
187 let uuid1 = UUID::new_dcom(timestamp, node_id1)
188 .expect("new_dcom should succeed for valid timestamp and node id");
189 let uuid2 = UUID::new_dcom(timestamp, node_id2)
190 .expect("new_dcom should succeed for valid timestamp and node id");
191
192 assert_ne!(uuid1, uuid2);
193
194 assert_eq!(&uuid1.bytes[10..16], &node_id1);
196 assert_eq!(&uuid2.bytes[10..16], &node_id2);
197 }
198
199 #[test]
200 fn test_new_dcom_nanosecond_precision() {
201 let node_id = sample_node_id();
202
203 let timestamp1 = UNIX_EPOCH + Duration::new(1000, 100); let timestamp2 = UNIX_EPOCH + Duration::new(1000, 199); let uuid1 = UUID::new_dcom(timestamp1, node_id)
208 .expect("new_dcom should succeed for valid timestamp and node id");
209 let uuid2 = UUID::new_dcom(timestamp2, node_id)
210 .expect("new_dcom should succeed for valid timestamp and node id");
211
212 assert_eq!(uuid1, uuid2);
214
215 let timestamp3 = UNIX_EPOCH + Duration::new(1000, 1000); let uuid3 = UUID::new_dcom(timestamp3, node_id)
218 .expect("new_dcom should succeed for valid timestamp and node id");
219
220 assert_ne!(uuid1, uuid3);
221 }
222
223 #[test]
224 fn test_new_dcom_far_future() {
225 let node_id = sample_node_id();
226
227 let far_future = UNIX_EPOCH + Duration::from_secs(365 * 24 * 3600 * 130); let result = UUID::new_dcom(far_future, node_id);
231 assert!(result.is_ok());
232
233 let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
234 assert_eq!(uuid.get_variant(), Variant::DCOM);
235 }
236
237 #[test]
238 fn test_new_dcom_timestamp_overflow() {
239 let node_id = sample_node_id();
240
241 let max_duration = Duration::new(u64::MAX / 10_000_000, 999_999_999);
244 let overflow_timestamp = UNIX_EPOCH + max_duration;
245
246 let result = UUID::new_dcom(overflow_timestamp, node_id);
247
248 assert_eq!(result, Err(UuidConstructionError::TimestampOverflow));
249 }
250
251 #[test]
252 fn test_new_dcom_all_zero_node_id() {
253 let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
254 let node_id = [0x00; 6];
255
256 let result = UUID::new_dcom(timestamp, node_id);
257 assert!(result.is_ok());
258
259 let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
260 assert_eq!(&uuid.bytes[10..16], &node_id);
261 }
262
263 #[test]
264 fn test_new_dcom_all_ff_node_id() {
265 let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
266 let node_id = [0xFF; 6];
267
268 let result = UUID::new_dcom(timestamp, node_id);
269 assert!(result.is_ok());
270
271 let uuid = result.expect("new_dcom should succeed for valid timestamp and node id");
272 assert_eq!(&uuid.bytes[10..16], &node_id);
273 }
274
275 #[test]
276 fn test_new_dcom_endianness() {
277 let timestamp = UNIX_EPOCH + Duration::from_secs(0x1234_5678);
278 let node_id = sample_node_id();
279
280 let uuid = UUID::new_dcom(timestamp, node_id)
281 .expect("new_dcom should succeed for valid timestamp and node id");
282
283 let expected_filetime = 0x1234_5678u64 * 10_000_000 + FILETIME_EPOCH_OFFSET;
285
286 let time_low_bytes = (expected_filetime as u32).to_le_bytes();
288 assert_eq!(&uuid.bytes[0..4], &time_low_bytes);
289
290 let time_mid_bytes = (((expected_filetime >> 32) & 0xFFFF) as u16).to_le_bytes();
292 assert_eq!(&uuid.bytes[4..6], &time_mid_bytes);
293
294 let time_hi_bytes = (((expected_filetime >> 48) & 0xFFFF) as u16).to_le_bytes();
296 assert_eq!(&uuid.bytes[6..8], &time_hi_bytes);
297 }
298
299 #[test]
300 fn test_new_dcom_variant_bits() {
301 let timestamp = UNIX_EPOCH + Duration::from_secs(1000);
302 let node_id = sample_node_id();
303
304 let uuid = UUID::new_dcom(timestamp, node_id)
305 .expect("new_dcom should succeed for valid timestamp and node id");
306
307 let byte_8 = uuid.bytes[8];
309 let variant_bits = (byte_8 & 0xE0) >> 5; assert_eq!(variant_bits, 0b110); }
312}