oxibonsai_runtime/
request_id.rs1use std::sync::atomic::{AtomicU64, Ordering};
33use std::time::{SystemTime, UNIX_EPOCH};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub struct RequestId {
40 high: u64,
41 low: u64,
42}
43
44impl RequestId {
45 pub fn new() -> Self {
47 let high = next_u64();
48 let low = next_u64();
49 Self::from_pair(high, low)
50 }
51
52 pub fn from_pair(high: u64, low: u64) -> Self {
55 let high = (high & !0x0000_0000_0000_F000) | 0x0000_0000_0000_4000;
57 let low = (low & !0xC000_0000_0000_0000) | 0x8000_0000_0000_0000;
59 Self { high, low }
60 }
61
62 pub fn as_hex(&self) -> String {
64 format!("{:016x}{:016x}", self.high, self.low)
65 }
66
67 pub fn as_uuid(&self) -> String {
69 let h = self.as_hex();
70 format!(
72 "{}-{}-{}-{}-{}",
73 &h[0..8],
74 &h[8..12],
75 &h[12..16],
76 &h[16..20],
77 &h[20..32]
78 )
79 }
80
81 pub fn high(&self) -> u64 {
83 self.high
84 }
85
86 pub fn low(&self) -> u64 {
88 self.low
89 }
90
91 pub fn from_hex(s: &str) -> Option<Self> {
95 if s.len() != 32 || !s.chars().all(|c| c.is_ascii_hexdigit()) {
96 return None;
97 }
98 let high = u64::from_str_radix(&s[0..16], 16).ok()?;
99 let low = u64::from_str_radix(&s[16..32], 16).ok()?;
100 Some(Self { high, low })
101 }
102
103 pub fn from_uuid(s: &str) -> Option<Self> {
105 if s.len() != 36 {
106 return None;
107 }
108 let mut buf = String::with_capacity(32);
110 for (i, c) in s.chars().enumerate() {
111 match i {
112 8 | 13 | 18 | 23 => {
113 if c != '-' {
114 return None;
115 }
116 }
117 _ => buf.push(c),
118 }
119 }
120 Self::from_hex(&buf)
121 }
122
123 pub fn as_bytes(&self) -> [u8; 16] {
130 let h = self.high.to_be_bytes();
131 let l = self.low.to_be_bytes();
132 let mut out = [0u8; 16];
133 out[..8].copy_from_slice(&h);
134 out[8..].copy_from_slice(&l);
135 out
136 }
137
138 pub fn from_bytes(bytes: [u8; 16]) -> Self {
145 let mut h_arr = [0u8; 8];
146 let mut l_arr = [0u8; 8];
147 h_arr.copy_from_slice(&bytes[..8]);
148 l_arr.copy_from_slice(&bytes[8..]);
149 Self {
150 high: u64::from_be_bytes(h_arr),
151 low: u64::from_be_bytes(l_arr),
152 }
153 }
154}
155
156impl Default for RequestId {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl std::fmt::Display for RequestId {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 write!(f, "{}", self.as_uuid())
165 }
166}
167
168static GLOBAL_STATE: AtomicU64 = AtomicU64::new(0);
171
172fn ensure_seeded() {
173 if GLOBAL_STATE.load(Ordering::Relaxed) == 0 {
174 let nanos = SystemTime::now()
176 .duration_since(UNIX_EPOCH)
177 .map(|d| d.as_nanos() as u64)
178 .unwrap_or(0xa3b1_c4d5_e6f7_8901);
179 let seed = nanos ^ 0x9E37_79B9_7F4A_7C15; let _ = GLOBAL_STATE.compare_exchange(0, seed, Ordering::Relaxed, Ordering::Relaxed);
181 }
182}
183
184fn next_u64() -> u64 {
185 ensure_seeded();
186 let prev = GLOBAL_STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
188 let mut z = prev.wrapping_add(0x9E37_79B9_7F4A_7C15);
189 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
190 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
191 z ^ (z >> 31)
192}
193
194#[cfg(test)]
197mod tests {
198 use super::*;
199 use std::collections::HashSet;
200
201 #[test]
202 fn new_is_unique() {
203 let mut set = HashSet::new();
204 for _ in 0..2000 {
205 let id = RequestId::new();
206 assert!(set.insert(id), "duplicate request id observed");
207 }
208 }
209
210 #[test]
211 fn hex_is_32_chars() {
212 let id = RequestId::new();
213 let h = id.as_hex();
214 assert_eq!(h.len(), 32);
215 assert!(h.chars().all(|c| c.is_ascii_hexdigit()));
216 }
217
218 #[test]
219 fn uuid_format_is_well_formed() {
220 let id = RequestId::new();
221 let s = id.as_uuid();
222 assert_eq!(s.len(), 36);
223 let parts: Vec<&str> = s.split('-').collect();
224 assert_eq!(parts.len(), 5);
225 assert_eq!(parts[0].len(), 8);
226 assert_eq!(parts[1].len(), 4);
227 assert_eq!(parts[2].len(), 4);
228 assert_eq!(parts[3].len(), 4);
229 assert_eq!(parts[4].len(), 12);
230 assert!(parts[2].starts_with('4'));
232 let variant = parts[3].chars().next().expect("non-empty variant nibble");
234 assert!(matches!(variant, '8' | '9' | 'a' | 'b'));
235 }
236
237 #[test]
238 fn from_pair_sets_version_and_variant() {
239 let id = RequestId::from_pair(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF);
240 let h = id.as_hex();
241 assert_eq!(&h[12..13], "4");
243 let v = h.chars().nth(16).expect("variant nibble");
245 assert!(matches!(v, '8' | '9' | 'a' | 'b'));
246 }
247
248 #[test]
249 fn round_trip_hex() {
250 let id = RequestId::new();
251 let s = id.as_hex();
252 let parsed = RequestId::from_hex(&s).expect("hex parse");
253 assert_eq!(id, parsed);
254 }
255
256 #[test]
257 fn round_trip_uuid() {
258 let id = RequestId::new();
259 let s = id.as_uuid();
260 let parsed = RequestId::from_uuid(&s).expect("uuid parse");
261 assert_eq!(id, parsed);
262 }
263
264 #[test]
265 fn rejects_bad_hex() {
266 assert!(RequestId::from_hex("").is_none());
267 assert!(RequestId::from_hex("too-short").is_none());
268 assert!(RequestId::from_hex(&"x".repeat(32)).is_none());
269 assert!(RequestId::from_hex(&"a".repeat(31)).is_none());
271 assert!(RequestId::from_hex(&"a".repeat(33)).is_none());
272 }
273
274 #[test]
275 fn rejects_bad_uuid() {
276 assert!(RequestId::from_uuid("not-a-uuid").is_none());
277 assert!(RequestId::from_uuid(&"a".repeat(36)).is_none());
279 assert!(RequestId::from_uuid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").is_none());
281 }
282
283 #[test]
284 fn display_uses_uuid_format() {
285 let id = RequestId::new();
286 let s = format!("{id}");
287 assert_eq!(s, id.as_uuid());
288 }
289
290 #[test]
291 fn as_bytes_round_trip() {
292 let id = RequestId::new();
293 let bytes = id.as_bytes();
294 let recovered = RequestId::from_bytes(bytes);
295 assert_eq!(id, recovered);
296 }
297
298 #[test]
299 fn as_bytes_big_endian_layout() {
300 let id = RequestId::from_pair(0x0123_4567_89AB_CDEF, 0xFEDC_BA98_7654_3210);
301 let bytes = id.as_bytes();
302 assert_eq!(bytes[0], 0x01);
304 assert_eq!(bytes[1], 0x23);
305 assert_eq!(bytes[6], 0x4D); let variant = bytes[8] >> 6;
308 assert_eq!(variant, 0b10);
309 }
310
311 #[test]
312 fn from_bytes_preserves_arbitrary_bytes() {
313 let bytes = [0u8; 16];
315 let id = RequestId::from_bytes(bytes);
316 assert_eq!(id.as_bytes(), bytes);
317 }
318
319 #[test]
320 fn high_low_recoverable() {
321 let id = RequestId::from_pair(0x1234_5678_9abc_def0, 0xfedc_ba98_7654_3210);
322 let h_hex = format!("{:016x}", id.high());
325 let l_hex = format!("{:016x}", id.low());
326 assert_eq!(id.as_hex(), format!("{h_hex}{l_hex}"));
327 }
328
329 #[test]
330 fn concurrent_generation_is_unique() {
331 use std::sync::Arc;
332 use std::sync::Mutex;
333 use std::thread;
334
335 let collected = Arc::new(Mutex::new(HashSet::new()));
336 let mut handles = Vec::new();
337 for _ in 0..8 {
338 let collected = Arc::clone(&collected);
339 handles.push(thread::spawn(move || {
340 let mut local = HashSet::new();
341 for _ in 0..500 {
342 local.insert(RequestId::new());
343 }
344 let mut g = collected.lock().expect("lock poisoned");
345 for id in local {
346 assert!(g.insert(id), "duplicate id from concurrent generation");
347 }
348 }));
349 }
350 for h in handles {
351 h.join().expect("thread panic");
352 }
353 assert_eq!(collected.lock().expect("lock").len(), 8 * 500);
354 }
355}