Skip to main content

logicaffeine_base/
uuid.rs

1//! UUID — a 128-bit universally-unique identifier (RFC 9562, superseding RFC 4122).
2//!
3//! Rolled in-house rather than wrapping a crate: the campaign builds first-class types, and owning the
4//! implementation is what lets us be byte-exact across every tier and benchmark it head-to-head. The
5//! value is a fixed `[u8; 16]` in network (big-endian) byte order, so it is `Copy`, `Ord` by bytes
6//! (which makes v6/v7 time-ordered ids sort chronologically), and hashes cheaply.
7//!
8//! Every standard version is here:
9//! - **nil** (all-zero) and **max** (all-one) — the two special ids.
10//! - **v1** (gregorian time + node) and **v6** (the same fields reordered to sort by time).
11//! - **v3** (MD5 of namespace ‖ name) and **v5** (SHA-1 of namespace ‖ name) — name-based, stable.
12//! - **v4** (random) and **v7** (Unix-millis time-ordered + random) — the two you reach for today.
13//! - **v8** (free-form / vendor-defined) — arbitrary bytes with the version+variant bits stamped.
14//!
15//! Generation takes the entropy/time as parameters (no ambient clock or RNG here) so it is pure and
16//! deterministic — the higher tiers seed it for byte-identical cross-tier output. Validated against
17//! the `uuid` crate (the name-based and random/time builders, parse, and display) in the tests.
18
19use core::fmt;
20
21use crate::hash::{md5, sha1};
22
23/// A 128-bit UUID, stored big-endian (RFC 9562 byte order).
24#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct Uuid([u8; 16]);
26
27/// Build `namespace ‖ name` and hand the slice to `f`, WITHOUT a heap allocation for any real name.
28/// A 240-byte inline stack buffer covers every practical v3/v5 input (DNS names, URLs, OIDs are far
29/// shorter); only a pathologically long name falls back to a `Vec`. The 16 namespace bytes always fit.
30#[inline]
31fn with_namespaced_input<R>(namespace: Uuid, name: &[u8], f: impl FnOnce(&[u8]) -> R) -> R {
32    const INLINE: usize = 256;
33    let total = 16 + name.len();
34    if total <= INLINE {
35        let mut buf = [0u8; INLINE];
36        buf[..16].copy_from_slice(&namespace.0);
37        buf[16..total].copy_from_slice(name);
38        f(&buf[..total])
39    } else {
40        let mut input = Vec::with_capacity(total);
41        input.extend_from_slice(&namespace.0);
42        input.extend_from_slice(name);
43        f(&input)
44    }
45}
46
47/// The RFC-defined variant of a UUID — which layout the variant/version bits follow.
48#[derive(Clone, Copy, PartialEq, Eq, Debug)]
49pub enum Variant {
50    /// Reserved, NCS backward compatibility (`0xx`).
51    Ncs,
52    /// The standard RFC 9562 / RFC 4122 layout (`10x`) — every version we generate.
53    Rfc4122,
54    /// Reserved, Microsoft GUID (`110`).
55    Microsoft,
56    /// Reserved for future definition (`111`).
57    Future,
58}
59
60impl Uuid {
61    /// The nil UUID — all 128 bits zero (`00000000-0000-0000-0000-000000000000`).
62    pub const NIL: Uuid = Uuid([0u8; 16]);
63    /// The max UUID — all 128 bits one (`ffffffff-ffff-ffff-ffff-ffffffffffff`), RFC 9562 §5.10.
64    pub const MAX: Uuid = Uuid([0xFFu8; 16]);
65
66    /// Namespace ID for fully-qualified domain names (RFC 9562 Appendix A).
67    pub const NAMESPACE_DNS: Uuid = Uuid([
68        0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
69    ]);
70    /// Namespace ID for URLs.
71    pub const NAMESPACE_URL: Uuid = Uuid([
72        0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
73    ]);
74    /// Namespace ID for ISO OIDs.
75    pub const NAMESPACE_OID: Uuid = Uuid([
76        0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
77    ]);
78    /// Namespace ID for X.500 DNs.
79    pub const NAMESPACE_X500: Uuid = Uuid([
80        0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
81    ]);
82
83    /// Wrap raw big-endian bytes verbatim (no version/variant stamping).
84    pub const fn from_bytes(bytes: [u8; 16]) -> Uuid {
85        Uuid(bytes)
86    }
87
88    /// The raw 16 big-endian bytes.
89    pub const fn to_bytes(self) -> [u8; 16] {
90        self.0
91    }
92
93    /// Borrow the raw 16 big-endian bytes.
94    pub const fn as_bytes(&self) -> &[u8; 16] {
95        &self.0
96    }
97
98    /// True for the all-zero nil UUID.
99    pub fn is_nil(&self) -> bool {
100        self.0 == [0u8; 16]
101    }
102
103    /// True for the all-one max UUID.
104    pub fn is_max(&self) -> bool {
105        self.0 == [0xFFu8; 16]
106    }
107
108    /// The version number (the high nibble of byte 6): 1–8 for the defined versions, 0 for nil,
109    /// 0xF for max. A pure read of the field — it does not assert the id was generated correctly.
110    pub fn version(&self) -> u8 {
111        self.0[6] >> 4
112    }
113
114    /// The variant (the high bits of byte 8). Everything we generate is [`Variant::Rfc4122`].
115    pub fn variant(&self) -> Variant {
116        let b = self.0[8];
117        if b & 0x80 == 0x00 {
118            Variant::Ncs
119        } else if b & 0xC0 == 0x80 {
120            Variant::Rfc4122
121        } else if b & 0xE0 == 0xC0 {
122            Variant::Microsoft
123        } else {
124            Variant::Future
125        }
126    }
127
128    /// Stamp the version nibble (byte 6) and the RFC-4122 variant bits (byte 8). Done as one `u128`
129    /// (register-resident) rather than two byte-indexed writes, so the value never spills to the stack:
130    /// byte 6's high nibble is bits `[76..80)` big-endian, byte 8's top two bits are `[62..64)`.
131    #[inline]
132    fn stamp(bytes: [u8; 16], version: u8) -> Uuid {
133        let mut v = u128::from_be_bytes(bytes);
134        v = (v & !(0xFu128 << 76)) | ((version as u128) << 76);
135        v = (v & !(0b11u128 << 62)) | (0b10u128 << 62);
136        Uuid(v.to_be_bytes())
137    }
138
139    /// Version 1: a 60-bit gregorian timestamp (100-ns ticks since 1582-10-15), a 14-bit clock
140    /// sequence, and a 48-bit node id. Layout is little-end-first on time (not time-sortable; that is
141    /// what v6 fixes).
142    pub fn new_v1(timestamp_100ns: u64, clock_seq: u16, node: [u8; 6]) -> Uuid {
143        let ts = timestamp_100ns & 0x0FFF_FFFF_FFFF_FFFF;
144        let time_low = (ts & 0xFFFF_FFFF) as u32;
145        let time_mid = ((ts >> 32) & 0xFFFF) as u16;
146        let time_hi = ((ts >> 48) & 0x0FFF) as u16;
147        let mut b = [0u8; 16];
148        b[0..4].copy_from_slice(&time_low.to_be_bytes());
149        b[4..6].copy_from_slice(&time_mid.to_be_bytes());
150        b[6..8].copy_from_slice(&time_hi.to_be_bytes());
151        b[8] = (clock_seq >> 8) as u8;
152        b[9] = (clock_seq & 0xFF) as u8;
153        b[10..16].copy_from_slice(&node);
154        Uuid::stamp(b, 1)
155    }
156
157    /// Version 6: the v1 fields with the timestamp reordered most-significant-first, so byte order
158    /// sorts chronologically (RFC 9562 §5.6) — a drop-in, sortable replacement for v1.
159    pub fn new_v6(timestamp_100ns: u64, clock_seq: u16, node: [u8; 6]) -> Uuid {
160        let ts = timestamp_100ns & 0x0FFF_FFFF_FFFF_FFFF;
161        let time_high = ((ts >> 28) & 0xFFFF_FFFF) as u32;
162        let time_mid = ((ts >> 12) & 0xFFFF) as u16;
163        let time_low = (ts & 0x0FFF) as u16;
164        let mut b = [0u8; 16];
165        b[0..4].copy_from_slice(&time_high.to_be_bytes());
166        b[4..6].copy_from_slice(&time_mid.to_be_bytes());
167        b[6..8].copy_from_slice(&time_low.to_be_bytes());
168        b[8] = (clock_seq >> 8) as u8;
169        b[9] = (clock_seq & 0xFF) as u8;
170        b[10..16].copy_from_slice(&node);
171        Uuid::stamp(b, 6)
172    }
173
174    /// Version 3: MD5 of `namespace` bytes followed by `name` (RFC 9562 §5.3). Name-based and stable —
175    /// the same namespace+name always yields the same id. This is the native REFERENCE ORACLE: the
176    /// language's `uuid_v3` is written in Logos (`assets/std/uuid.lg`, over the Logos `md5Digest`) and is
177    /// proven byte-exact against this and the `uuid`/`md-5` crates — it is not on any language path.
178    pub fn new_v3(namespace: Uuid, name: &[u8]) -> Uuid {
179        with_namespaced_input(namespace, name, |input| Uuid::stamp(md5(input), 3))
180    }
181
182    /// Version 5: SHA-1 of `namespace` bytes followed by `name`, truncated to 16 bytes (RFC 9562
183    /// §5.5). Name-based and stable; preferred over v3. The native REFERENCE ORACLE for the Logos
184    /// `uuid_v5` (uuid.lg, over `sha1Digest`) — see [`Uuid::new_v3`].
185    pub fn new_v5(namespace: Uuid, name: &[u8]) -> Uuid {
186        with_namespaced_input(namespace, name, |input| {
187            let digest = sha1(input);
188            let mut b = [0u8; 16];
189            b.copy_from_slice(&digest[..16]);
190            Uuid::stamp(b, 5)
191        })
192    }
193
194    /// Version 4: 122 bits of supplied randomness (RFC 9562 §5.4). The 6 version/variant bits are
195    /// overwritten, so all 16 bytes of entropy may be passed.
196    #[inline]
197    pub fn new_v4(random: [u8; 16]) -> Uuid {
198        Uuid::stamp(random, 4)
199    }
200
201    /// Version 7: a 48-bit big-endian Unix-millisecond timestamp followed by 74 bits of randomness
202    /// (RFC 9562 §5.7). Time-ordered (byte order sorts by creation time) — the modern default.
203    #[inline]
204    pub fn new_v7(unix_ms: u64, random: [u8; 10]) -> Uuid {
205        let mut b = [0u8; 16];
206        let ms = unix_ms & 0xFFFF_FFFF_FFFF;
207        b[0..6].copy_from_slice(&ms.to_be_bytes()[2..8]);
208        b[6..16].copy_from_slice(&random);
209        Uuid::stamp(b, 7)
210    }
211
212    /// Version 8: vendor/experimental — the 16 bytes are taken as given, with only the version and
213    /// variant bits stamped (RFC 9562 §5.8).
214    pub fn new_v8(bytes: [u8; 16]) -> Uuid {
215        Uuid::stamp(bytes, 8)
216    }
217
218    /// Parse a UUID from text. Accepts the canonical hyphenated form, the 32-hex simple form, the
219    /// braced `{…}` form, and the `urn:uuid:…` form; case-insensitive. Returns `None` on any other
220    /// shape, a bad length, or a non-hex digit — never panics.
221    pub fn parse(input: &str) -> Option<Uuid> {
222        let s = input.trim();
223        // Fast path: the canonical 36-char hyphenated form — the overwhelmingly common case. A
224        // byte-table hex decode with no per-char branching; any invalid digit poisons `bad` to ≥16.
225        if s.len() == 36 {
226            // Fast path: the canonical 36-char hyphenated form — the overwhelmingly common case.
227            return Self::parse_canonical(s.as_bytes().try_into().unwrap());
228        }
229        // Strip an optional `urn:uuid:` prefix (case-insensitive) and `{…}` braces.
230        let s = s.strip_prefix("urn:uuid:").or_else(|| s.strip_prefix("URN:UUID:")).unwrap_or(s);
231        let s = s.strip_prefix('{').and_then(|t| t.strip_suffix('}')).unwrap_or(s);
232
233        // Collect exactly the hex digits, rejecting any non-hyphen, non-hex character, and require the
234        // hyphens (when present) to sit at the canonical 8-4-4-4-12 positions.
235        let bytes = s.as_bytes();
236        let mut hex = [0u8; 32];
237        let mut n = 0usize;
238        match bytes.len() {
239            36 => {
240                // Canonical: hyphens at indices 8, 13, 18, 23.
241                for (i, &c) in bytes.iter().enumerate() {
242                    if matches!(i, 8 | 13 | 18 | 23) {
243                        if c != b'-' {
244                            return None;
245                        }
246                    } else {
247                        if n >= 32 {
248                            return None;
249                        }
250                        hex[n] = c;
251                        n += 1;
252                    }
253                }
254            }
255            32 => hex.copy_from_slice(bytes),
256            _ => return None,
257        }
258        if n != 0 && n != 32 {
259            return None;
260        }
261
262        let mut out = [0u8; 16];
263        for i in 0..16 {
264            let hi = hex_val(hex[2 * i])?;
265            let lo = hex_val(hex[2 * i + 1])?;
266            out[i] = (hi << 4) | lo;
267        }
268        Some(Uuid(out))
269    }
270
271    /// Decode one canonical 36-byte hyphenated record — the shared fast path behind [`Uuid::parse`] and
272    /// [`Uuid::parse_many`]. Checks the four hyphens, then the SIMD `pshufb` decode (statically dispatched
273    /// under `target-cpu=native`, runtime-detected otherwise, scalar-table fallback). `None` if malformed.
274    #[inline]
275    fn parse_canonical(arr: &[u8; 36]) -> Option<Uuid> {
276        if arr[8] != b'-' || arr[13] != b'-' || arr[18] != b'-' || arr[23] != b'-' {
277            return None;
278        }
279        #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
280        {
281            return unsafe { x86_hex::parse_hyphenated(arr) }.map(Uuid);
282        }
283        #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
284        {
285            if x86_hex::available() {
286                return unsafe { x86_hex::parse_hyphenated(arr) }.map(Uuid);
287            }
288        }
289        #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
290        {
291            let mut out = [0u8; 16];
292            let mut bad = 0u8;
293            // The 16 byte positions: pairs of hex indices, skipping the four hyphens.
294            const PAIRS: [usize; 16] = [0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34];
295            for (i, &p) in PAIRS.iter().enumerate() {
296                let hi = HEX_DECODE[arr[p] as usize];
297                let lo = HEX_DECODE[arr[p + 1] as usize];
298                bad |= hi | lo;
299                out[i] = (hi << 4) | (lo & 0x0f);
300            }
301            if bad < 16 {
302                Some(Uuid(out))
303            } else {
304                None
305            }
306        }
307    }
308
309    /// Parse many canonical 36-char UUIDs packed back-to-back (`36·n` bytes) into a `Vec<Uuid>` — the
310    /// bulk read path (a DB column / log-ingest stream). Loops the SIMD decode core directly with no
311    /// per-id `trim`/dispatch overhead, into a single pre-sized allocation. `None` if the length isn't a
312    /// multiple of 36 or any record is malformed. Pairs with [`encode_many`](encode_many) (the bulk write path).
313    pub fn parse_many(packed: &[u8]) -> Option<Vec<Uuid>> {
314        if packed.is_empty() || packed.len() % 36 != 0 {
315            return None;
316        }
317        #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
318        {
319            return unsafe { x86_hex::parse_batch(packed) };
320        }
321        #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
322        {
323            if x86_hex::available() {
324                return unsafe { x86_hex::parse_batch(packed) };
325            }
326        }
327        #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
328        {
329            let n = packed.len() / 36;
330            let mut out = Vec::with_capacity(n);
331            for chunk in packed.chunks_exact(36) {
332                let rec: &[u8; 36] = chunk.try_into().unwrap();
333                out.push(Self::parse_canonical(rec)?);
334            }
335            Some(out)
336        }
337    }
338}
339
340/// `HEX_DECODE[c]` is the nibble value (0–15) of ASCII hex char `c`, or `0xFF` (≥16) for any
341/// non-hex byte — so a single `|`-accumulation over a run detects an invalid digit branch-free.
342const HEX_DECODE: [u8; 256] = {
343    let mut t = [0xFFu8; 256];
344    let mut i = 0u8;
345    while i < 10 {
346        t[(b'0' + i) as usize] = i;
347        i += 1;
348    }
349    let mut i = 0u8;
350    while i < 6 {
351        t[(b'a' + i) as usize] = 10 + i;
352        t[(b'A' + i) as usize] = 10 + i;
353        i += 1;
354    }
355    t
356};
357
358fn hex_val(c: u8) -> Option<u8> {
359    match c {
360        b'0'..=b'9' => Some(c - b'0'),
361        b'a'..=b'f' => Some(c - b'a' + 10),
362        b'A'..=b'F' => Some(c - b'A' + 10),
363        _ => None,
364    }
365}
366
367/// `HEX_PAIRS[b]` is the two lowercase ASCII hex chars of byte `b`, packed — one table lookup per
368/// byte instead of two nibble lookups.
369const HEX_PAIRS: [[u8; 2]; 256] = {
370    const HEX: &[u8; 16] = b"0123456789abcdef";
371    let mut t = [[0u8; 2]; 256];
372    let mut i = 0;
373    while i < 256 {
374        t[i] = [HEX[i >> 4], HEX[i & 0xf]];
375        i += 1;
376    }
377    t
378};
379
380/// Write a UUID's canonical 36-byte text into a caller buffer (no allocation) — the alloc-free hot
381/// path for `Display` and for BULK formatting (fill one big buffer, zero per-id allocations). Uses an
382/// SSSE3 `pshufb` nibble→hex encode when available, else the byte-pair table.
383#[inline]
384pub fn encode_canonical(bytes: &[u8; 16], buf: &mut [u8; 36]) {
385    // Under `target-cpu=native` the SSSE3 detect is compiled away and this is a direct SIMD call.
386    #[cfg(all(target_arch = "x86_64", target_feature = "ssse3"))]
387    {
388        // SAFETY: ssse3 is statically enabled for this build.
389        return unsafe { x86_hex::encode_canonical(bytes, buf) };
390    }
391    #[cfg(all(target_arch = "x86_64", not(target_feature = "ssse3")))]
392    {
393        if x86_hex::available() {
394            // SAFETY: guarded by the ssse3 feature detection.
395            unsafe { x86_hex::encode_canonical(bytes, buf) };
396            return;
397        }
398    }
399    #[cfg(not(all(target_arch = "x86_64", target_feature = "ssse3")))]
400    encode_canonical_scalar(bytes, buf);
401}
402
403/// Portable byte-pair-table encode (the fallback / non-x86 path).
404#[inline]
405fn encode_canonical_scalar(bytes: &[u8; 16], buf: &mut [u8; 36]) {
406    let mut j = 0;
407    let mut write = |buf: &mut [u8; 36], range: std::ops::Range<usize>, j: &mut usize| {
408        for &byte in &bytes[range] {
409            let pair = HEX_PAIRS[byte as usize];
410            buf[*j] = pair[0];
411            buf[*j + 1] = pair[1];
412            *j += 2;
413        }
414    };
415    write(buf, 0..4, &mut j);
416    buf[j] = b'-';
417    j += 1;
418    write(buf, 4..6, &mut j);
419    buf[j] = b'-';
420    j += 1;
421    write(buf, 6..8, &mut j);
422    buf[j] = b'-';
423    j += 1;
424    write(buf, 8..10, &mut j);
425    buf[j] = b'-';
426    j += 1;
427    write(buf, 10..16, &mut j);
428}
429
430/// SSSE3 hex encode for a UUID: one `pshufb` turns 16 nibbles into 16 hex chars, done twice
431/// (high/low), interleaved with `punpck`, then scattered into the `8-4-4-4-12` layout.
432#[cfg(target_arch = "x86_64")]
433mod x86_hex {
434    /// The SSSE3 (`pshufb`) instruction this encode needs; cached by std after the first call.
435    #[inline]
436    pub fn available() -> bool {
437        std::is_x86_feature_detected!("ssse3")
438    }
439
440    #[target_feature(enable = "ssse3,sse2")]
441    pub unsafe fn encode_canonical(bytes: &[u8; 16], buf: &mut [u8; 36]) {
442        use core::arch::x86_64::*;
443        let v = _mm_loadu_si128(bytes.as_ptr() as *const __m128i);
444        let mask0f = _mm_set1_epi8(0x0f);
445        // Per byte: high nibble = (byte >> 4) & 0x0f, low nibble = byte & 0x0f.
446        let hi = _mm_and_si128(_mm_srli_epi16(v, 4), mask0f);
447        let lo = _mm_and_si128(v, mask0f);
448        let lut = _mm_setr_epi8(
449            b'0' as i8, b'1' as i8, b'2' as i8, b'3' as i8, b'4' as i8, b'5' as i8, b'6' as i8,
450            b'7' as i8, b'8' as i8, b'9' as i8, b'a' as i8, b'b' as i8, b'c' as i8, b'd' as i8,
451            b'e' as i8, b'f' as i8,
452        );
453        let hi_hex = _mm_shuffle_epi8(lut, hi);
454        let lo_hex = _mm_shuffle_epi8(lut, lo);
455        // Interleave to (hi0,lo0,hi1,lo1,…): chars 0–15 in `h0`, 16–31 in `h1`.
456        let h0 = _mm_unpacklo_epi8(hi_hex, lo_hex);
457        let h1 = _mm_unpackhi_epi8(hi_hex, lo_hex);
458        // SIMD scatter into the 8-4-4-4-12 layout — `pshufb` places the hex + zeros the dash slots,
459        // `palignr` bridges the c14/c15 that straddle h0/h1 so out[16..32] is one shuffle, dashes OR'd
460        // in. Two 16-byte stores + a 4-byte tail; measured ~1.23× the per-range `copy_from_slice`.
461        let mask_lo = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, -128, 8, 9, 10, 11, -128, 12, 13);
462        let dash_lo = _mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0x2d, 0, 0, 0, 0, 0x2d, 0, 0);
463        let out_lo = _mm_or_si128(_mm_shuffle_epi8(h0, mask_lo), dash_lo);
464        let mid = _mm_alignr_epi8(h1, h0, 14); // [c14, c15, c16..c29]
465        let mask_mid = _mm_setr_epi8(0, 1, -128, 2, 3, 4, 5, -128, 6, 7, 8, 9, 10, 11, 12, 13);
466        let dash_mid = _mm_setr_epi8(0, 0, 0x2d, 0, 0, 0, 0, 0x2d, 0, 0, 0, 0, 0, 0, 0, 0);
467        let out_mid = _mm_or_si128(_mm_shuffle_epi8(mid, mask_mid), dash_mid);
468        _mm_storeu_si128(buf.as_mut_ptr() as *mut __m128i, out_lo);
469        _mm_storeu_si128(buf.as_mut_ptr().add(16) as *mut __m128i, out_mid);
470        let mut t = [0u8; 16];
471        _mm_storeu_si128(t.as_mut_ptr() as *mut __m128i, h1);
472        buf[32..36].copy_from_slice(&t[12..16]); // c28-31
473    }
474
475    /// ASCII hex chars → nibble values (0–15), one lane each: `(c & 0x0f) + 9·((c & 0x40) >> 6)`.
476    #[inline]
477    #[target_feature(enable = "ssse3,sse2")]
478    unsafe fn nibbles(chars: core::arch::x86_64::__m128i) -> core::arch::x86_64::__m128i {
479        use core::arch::x86_64::*;
480        let lo = _mm_and_si128(chars, _mm_set1_epi8(0x0f));
481        let hi = _mm_srli_epi16(_mm_and_si128(chars, _mm_set1_epi8(0x40)), 6); // per byte {0,1}
482        let hi8 = _mm_slli_epi16(hi, 3); // per byte {0,8}
483        _mm_add_epi8(_mm_add_epi8(lo, hi8), hi) // lo + 9·hi
484    }
485
486    /// Per-lane hex-validity MASK (`0xFF` where the lane is `0-9A-Fa-f`, else `0x00`), case-folded — the
487    /// value form of a boolean range check, so a batch can `&`-accumulate one mask across many records
488    /// and pay the single `movemask`+branch once per column instead of once per record.
489    #[inline]
490    #[target_feature(enable = "ssse3,sse2")]
491    unsafe fn hex_ok(chars: core::arch::x86_64::__m128i) -> core::arch::x86_64::__m128i {
492        use core::arch::x86_64::*;
493        let cf = _mm_or_si128(chars, _mm_set1_epi8(0x20)); // fold 'A'-'F' → 'a'-'f'
494        let clamp = |lo: i8, hi: i8| {
495            _mm_cmpeq_epi8(_mm_min_epu8(_mm_max_epu8(cf, _mm_set1_epi8(lo)), _mm_set1_epi8(hi)), cf)
496        };
497        _mm_or_si128(clamp(0x30, 0x39), clamp(0x61, 0x66))
498    }
499
500    /// The branch-free decode core shared by the single and bulk parsers. Two `pshufb` gathers strip the
501    /// four hyphens; `pmaddubsw` with weights `[16, 1, …]` fuses each nibble pair and `packuswb` narrows
502    /// to the first 14 bytes; the last two bytes (`string[32..36]`, hyphen-free) decode with the same
503    /// branchless `(c & 0xf) + 9·(c >> 6)` nibble. Writes all 16 bytes UNCONDITIONALLY (garbage for
504    /// non-hex input, which the caller rejects) and RETURNS the per-lane hex-validity mask of the gather.
505    #[inline]
506    #[target_feature(enable = "ssse3,sse2")]
507    unsafe fn decode_core(b: &[u8; 36], out: &mut [u8; 16]) -> core::arch::x86_64::__m128i {
508        use core::arch::x86_64::*;
509        let l0 = _mm_loadu_si128(b.as_ptr() as *const __m128i); // string[0..16]
510        let l1 = _mm_loadu_si128(b.as_ptr().add(16) as *const __m128i); // string[16..32]
511        // Gather each half's hex chars, dropping the hyphens. The two padding lanes reuse index 0 (a
512        // real hex char), so validity never trips on them and their (discarded) output byte is safe.
513        let mask_a = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 0, 0);
514        let mask_b = _mm_setr_epi8(0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0);
515        let ra = _mm_shuffle_epi8(l0, mask_a);
516        let rb = _mm_shuffle_epi8(l1, mask_b);
517        // Bytes `[16, 1, 16, 1, …]` as little-endian 16-bit lanes `0x0110`.
518        let weights = _mm_set1_epi16(0x0110);
519        let ba = _mm_packus_epi16(_mm_maddubs_epi16(nibbles(ra), weights), _mm_setzero_si128());
520        let bb = _mm_packus_epi16(_mm_maddubs_epi16(nibbles(rb), weights), _mm_setzero_si128());
521        _mm_storel_epi64(out.as_mut_ptr() as *mut __m128i, ba); // out[0..8] (byte 7 discarded)
522        _mm_storel_epi64(out.as_mut_ptr().add(7) as *mut __m128i, bb); // out[7..15] (byte 14 discarded)
523        // Tail — string[32..36], no hyphen. The `& 0x0f` keeps the nibble in range for garbage input so
524        // the unconditional `<< 4` cannot debug-overflow (valid nibbles are unaffected); rejection is
525        // the caller's job via the returned mask + the scalar-tail table check.
526        let nib = |c: u8| ((c & 0x0f) + 9 * (c >> 6)) & 0x0f;
527        out[14] = (nib(b[32]) << 4) | nib(b[33]);
528        out[15] = (nib(b[34]) << 4) | nib(b[35]);
529        _mm_and_si128(hex_ok(ra), hex_ok(rb))
530    }
531
532    /// SSSE3 hex DECODE for one canonical 36-char UUID. The caller guarantees `'-'` at 8/13/18/23.
533    /// Returns `None` on any non-hex digit — one `movemask` over the [`decode_core`] mask plus a table
534    /// check of the two scalar-tail bytes.
535    #[target_feature(enable = "ssse3,sse2")]
536    pub unsafe fn parse_hyphenated(b: &[u8; 36]) -> Option<[u8; 16]> {
537        use core::arch::x86_64::*;
538        let mut out = [0u8; 16];
539        let mask = decode_core(b, &mut out);
540        let tail = super::HEX_DECODE[b[32] as usize]
541            | super::HEX_DECODE[b[33] as usize]
542            | super::HEX_DECODE[b[34] as usize]
543            | super::HEX_DECODE[b[35] as usize];
544        if _mm_movemask_epi8(mask) == 0xffff && tail < 16 {
545            Some(out)
546        } else {
547            None
548        }
549    }
550
551    /// Bulk parse of packed 36-char records — the batch-validated fast path. Every record decodes through
552    /// the branch-free [`decode_core`], and the three validity channels accumulate WITHOUT branching (`&`
553    /// the hex-lane masks, `|` the scalar-tail nibbles, `|` the XORed hyphen slots), so the single
554    /// `movemask`+branch is paid once for the whole column instead of once per record — that per-record
555    /// `movemask`+branch is exactly what serialized the loop (measured ~3× the decode itself). A valid
556    /// column (the overwhelming common case) returns at trusted-decode speed; a single bad byte anywhere
557    /// sinks the batch to `None`, byte-for-byte matching the per-record parser. `packed.len()` is a
558    /// nonzero multiple of 36 (the caller checks).
559    #[target_feature(enable = "ssse3,sse2")]
560    pub unsafe fn parse_batch(packed: &[u8]) -> Option<Vec<super::Uuid>> {
561        use core::arch::x86_64::*;
562        let n = packed.len() / 36;
563        let mut out: Vec<super::Uuid> = Vec::with_capacity(n);
564        let mut acc = _mm_set1_epi8(-1i8); // all lanes 0xFF
565        let mut tail_bad = 0u8;
566        let mut dash_bad = 0u8;
567        for chunk in packed.chunks_exact(36) {
568            let b: &[u8; 36] = chunk.try_into().unwrap();
569            dash_bad |= (b[8] ^ b'-') | (b[13] ^ b'-') | (b[18] ^ b'-') | (b[23] ^ b'-');
570            tail_bad |= super::HEX_DECODE[b[32] as usize]
571                | super::HEX_DECODE[b[33] as usize]
572                | super::HEX_DECODE[b[34] as usize]
573                | super::HEX_DECODE[b[35] as usize];
574            let mut rec = [0u8; 16];
575            acc = _mm_and_si128(acc, decode_core(b, &mut rec));
576            out.push(super::Uuid(rec));
577        }
578        if dash_bad == 0 && tail_bad < 16 && _mm_movemask_epi8(acc) == 0xffff {
579            Some(out)
580        } else {
581            None
582        }
583    }
584}
585
586/// Format many UUIDs into ONE contiguous buffer (each 36 bytes, `\n`-free), zero per-id allocation —
587/// the bulk path a database column / log stream wants. Returns the packed `36·n`-byte buffer.
588pub fn encode_many(ids: &[Uuid]) -> Vec<u8> {
589    // Per-id SSSE3 encode, whose scatter is now SIMD (`pshufb`/`palignr`) — that's where bulk format
590    // spends its time. Controlled interleaved A/B (contention-matched): the SIMD scatter beat the
591    // per-range `copy_from_slice` by median 1.23× (whole distribution above 1.0). An AVX2 2-wide *encode*
592    // was separately a wash — the encode was never the bottleneck; the scatter was.
593    let mut out = vec![0u8; ids.len() * 36];
594    for (i, id) in ids.iter().enumerate() {
595        let slot: &mut [u8; 36] = (&mut out[i * 36..i * 36 + 36]).try_into().unwrap();
596        encode_canonical(&id.0, slot);
597    }
598    out
599}
600
601impl fmt::Display for Uuid {
602    /// Canonical lowercase hyphenated form, `8-4-4-4-12` (RFC 9562 §4).
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        let mut buf = [0u8; 36];
605        encode_canonical(&self.0, &mut buf);
606        // SAFETY: every byte written is ASCII (`0-9a-f` or `-`), so the buffer is valid UTF-8.
607        f.write_str(unsafe { core::str::from_utf8_unchecked(&buf) })
608    }
609}
610
611impl fmt::Debug for Uuid {
612    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613        write!(f, "Uuid({self})")
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    #[test]
622    fn nil_and_max_render_canonically() {
623        assert_eq!(Uuid::NIL.to_string(), "00000000-0000-0000-0000-000000000000");
624        assert_eq!(Uuid::MAX.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
625        assert!(Uuid::NIL.is_nil());
626        assert!(Uuid::MAX.is_max());
627        assert_eq!(Uuid::NIL.version(), 0);
628        assert_eq!(Uuid::MAX.version(), 0xF);
629    }
630
631    #[test]
632    fn parse_round_trips_every_accepted_form() {
633        let canonical = "550e8400-e29b-41d4-a716-446655440000";
634        let u = Uuid::parse(canonical).unwrap();
635        assert_eq!(u.to_string(), canonical);
636        // Simple, braced, urn, uppercase — all parse to the same value.
637        assert_eq!(Uuid::parse("550e8400e29b41d4a716446655440000").unwrap(), u);
638        assert_eq!(Uuid::parse("{550e8400-e29b-41d4-a716-446655440000}").unwrap(), u);
639        assert_eq!(Uuid::parse("urn:uuid:550e8400-e29b-41d4-a716-446655440000").unwrap(), u);
640        assert_eq!(Uuid::parse("550E8400-E29B-41D4-A716-446655440000").unwrap(), u);
641        assert_eq!(Uuid::parse("  550e8400-e29b-41d4-a716-446655440000  ").unwrap(), u);
642    }
643
644    #[test]
645    fn parse_rejects_malformed_input() {
646        assert_eq!(Uuid::parse(""), None);
647        assert_eq!(Uuid::parse("not-a-uuid"), None);
648        assert_eq!(Uuid::parse("550e8400-e29b-41d4-a716-44665544000"), None); // too short
649        assert_eq!(Uuid::parse("550e8400-e29b-41d4-a716-4466554400000"), None); // too long
650        assert_eq!(Uuid::parse("550e8400xe29b-41d4-a716-446655440000"), None); // hyphen slot wrong
651        assert_eq!(Uuid::parse("ZZZe8400-e29b-41d4-a716-446655440000"), None); // non-hex
652    }
653
654    #[test]
655    fn parse_validates_every_hex_position_and_case() {
656        // The SIMD path must accept both cases and the extremes, byte-identical to the scalar table.
657        assert_eq!(Uuid::parse("ffffffff-ffff-ffff-ffff-ffffffffffff").unwrap(), Uuid::MAX);
658        assert_eq!(Uuid::parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF").unwrap(), Uuid::MAX);
659        assert_eq!(Uuid::parse("00000000-0000-0000-0000-000000000000").unwrap(), Uuid::NIL);
660        let base = "550e8400-e29b-41d4-a716-446655440000";
661        assert_eq!(
662            Uuid::parse("550E8400-e29b-41D4-A716-446655440000").unwrap(),
663            Uuid::parse(base).unwrap(),
664            "mixed-case canonical must equal lowercase",
665        );
666        // Corrupt EVERY hex slot in turn with each boundary char adjacent to a valid range — the
667        // parallel range check must reject all of them at all 32 positions (catches an off-by-one in
668        // the SIMD validation, and the maddubs/pshufb lane layout via the value cases below).
669        for i in 0..36 {
670            if matches!(i, 8 | 13 | 18 | 23) {
671                continue; // hyphen slots handled separately
672            }
673            for c in [b'g', b'G', b':', b'/', b'@', b'`', b' ', 0x00u8, 0xff] {
674                let mut bad = base.as_bytes().to_vec();
675                bad[i] = c;
676                if let Ok(s) = std::str::from_utf8(&bad) {
677                    assert_eq!(Uuid::parse(s), None, "must reject byte {c:#x} at position {i}");
678                }
679            }
680        }
681    }
682
683    #[test]
684    fn parse_many_round_trips_a_packed_column() {
685        // Build a packed column of ids, format them, and bulk-parse the buffer back — must equal.
686        let mut ids = Vec::new();
687        let mut st = 0x1234_5678_9abc_def0u64;
688        for _ in 0..257 {
689            let mut b = [0u8; 16];
690            for x in b.iter_mut() {
691                st = st.wrapping_mul(6364136223846793005).wrapping_add(1);
692                *x = (st >> 56) as u8;
693            }
694            ids.push(Uuid::from_bytes(b));
695        }
696        let packed = encode_many(&ids);
697        assert_eq!(Uuid::parse_many(&packed).unwrap(), ids);
698        // Malformed inputs are all rejected: empty and a non-multiple length.
699        assert_eq!(Uuid::parse_many(&[]), None);
700        assert_eq!(Uuid::parse_many(&packed[..packed.len() - 1]), None);
701        // A single bad byte anywhere in ANY record must sink the whole batch — the batch-validated
702        // fast path accumulates three independent validity channels (the SIMD hex-lane mask, the
703        // scalar tail digits, and the four hyphens), so corruption is probed in each channel, at the
704        // first record, an interior record, and the very last record. Positions 8/13/18/23 are the
705        // hyphens; 32–35 are the scalar tail; everything else is a SIMD-lane hex digit.
706        let n = ids.len();
707        for &rec in &[0usize, 3, n - 1] {
708            for &pos in &[0usize, 5, 8, 12, 18, 23, 30, 32, 35] {
709                let mut bad = packed.clone();
710                let byte = 36 * rec + pos;
711                bad[byte] = if pos == 8 || pos == 13 || pos == 18 || pos == 23 {
712                    b'f' // a hyphen slot filled with a hex digit — dash channel must still reject
713                } else {
714                    b'z' // a hex slot filled with a non-hex byte — hex/tail channel must reject
715                };
716                assert_eq!(
717                    Uuid::parse_many(&bad),
718                    None,
719                    "record {rec} byte {pos} corruption not rejected"
720                );
721            }
722        }
723    }
724
725    #[test]
726    fn name_based_versions_match_the_rfc_worked_examples() {
727        // RFC-published name-based ids under the DNS namespace.
728        let v3 = Uuid::new_v3(Uuid::NAMESPACE_DNS, b"www.example.com");
729        assert_eq!(v3.version(), 3);
730        assert_eq!(v3.variant(), Variant::Rfc4122);
731        assert_eq!(v3.to_string(), "5df41881-3aed-3515-88a7-2f4a814cf09e");
732
733        let v5 = Uuid::new_v5(Uuid::NAMESPACE_DNS, b"www.example.com");
734        assert_eq!(v5.version(), 5);
735        assert_eq!(v5.variant(), Variant::Rfc4122);
736        assert_eq!(v5.to_string(), "2ed6657d-e927-568b-95e1-2665a8aea6a2");
737    }
738
739    #[test]
740    fn version_and_variant_bits_are_correct_for_every_generated_version() {
741        let node = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
742        let cases: [(Uuid, u8); 7] = [
743            (Uuid::new_v1(0x1234_5678_9ABC, 0x0102, node), 1),
744            (Uuid::new_v3(Uuid::NAMESPACE_URL, b"a"), 3),
745            (Uuid::new_v4([0xAB; 16]), 4),
746            (Uuid::new_v5(Uuid::NAMESPACE_URL, b"a"), 5),
747            (Uuid::new_v6(0x1234_5678_9ABC, 0x0102, node), 6),
748            (Uuid::new_v7(0x0190_0000_0000, [0x11; 10]), 7),
749            (Uuid::new_v8([0x77; 16]), 8),
750        ];
751        for (u, want) in cases {
752            assert_eq!(u.version(), want, "version nibble for v{want}");
753            assert_eq!(u.variant(), Variant::Rfc4122, "variant for v{want}");
754            // Round-trips through canonical text.
755            assert_eq!(Uuid::parse(&u.to_string()).unwrap(), u, "round trip v{want}");
756        }
757    }
758
759    #[test]
760    fn v7_is_time_ordered_by_bytes() {
761        // Two v7 ids one millisecond apart sort in time order regardless of the random tail.
762        let earlier = Uuid::new_v7(1000, [0xFF; 10]);
763        let later = Uuid::new_v7(1001, [0x00; 10]);
764        assert!(earlier < later, "v7 must sort by timestamp: {earlier} !< {later}");
765    }
766
767    #[test]
768    fn differential_against_the_uuid_crate() {
769        // Name-based (deterministic) versions, byte-for-byte against the reference crate.
770        for name in ["", "a", "www.example.com", "the quick brown fox", "🦀"] {
771            let ours = Uuid::new_v3(Uuid::NAMESPACE_DNS, name.as_bytes());
772            let theirs = uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, name.as_bytes());
773            assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v3 differs for {name:?}");
774
775            let ours = Uuid::new_v5(Uuid::NAMESPACE_URL, name.as_bytes());
776            let theirs = uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_URL, name.as_bytes());
777            assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v5 differs for {name:?}");
778        }
779
780        // v4 / v7 from identical entropy/time must equal the crate's builder output.
781        let rand16 = [
782            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
783        ];
784        let ours = Uuid::new_v4(rand16);
785        let theirs = uuid::Builder::from_random_bytes(rand16).into_uuid();
786        assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v4 differs");
787
788        let rand10 = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc];
789        let ms: u64 = 0x0190_2233_4455;
790        let ours = Uuid::new_v7(ms, rand10);
791        let theirs = uuid::Builder::from_unix_timestamp_millis(ms, &rand10).into_uuid();
792        assert_eq!(ours.to_bytes(), *theirs.as_bytes(), "v7 differs");
793
794        // Parse + display agree with the crate over random ids.
795        let mut state: u64 = 0xDEAD_BEEF_CAFE_F00D;
796        for _ in 0..2000 {
797            let mut bytes = [0u8; 16];
798            for byte in bytes.iter_mut() {
799                state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
800                *byte = (state >> 56) as u8;
801            }
802            let ours = Uuid::from_bytes(bytes);
803            let theirs = uuid::Uuid::from_bytes(bytes);
804            assert_eq!(ours.to_string(), theirs.to_string(), "display differs");
805            assert_eq!(Uuid::parse(&theirs.to_string()).unwrap(), ours, "parse differs");
806            assert_eq!(ours.version(), theirs.get_version_num() as u8, "version differs");
807        }
808    }
809}