(*--------------------------------------------------------------------------- Copyright (c) 2008 The uuidm programmers. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (* Bits *) type bits62 = int64 type bits4 = int type bits12 = int (* Hashing *) let md5 = Digest.string let sha_1 s = (* Based on pseudo-code of RFC 3174. Slow and ugly but does the job. *) let sha_1_pad s = let len = String.length s in let blen = 8 * len in let rem = len mod 64 in let mlen = if rem > 55 then len + 128 - rem else len + 64 - rem in let m = Bytes.create mlen in Bytes.blit_string s 0 m 0 len; Bytes.fill m len (mlen - len) '\x00'; Bytes.set m len '\x80'; if Sys.word_size > 32 then begin Bytes.set_uint8 m (mlen - 8) (blen lsr 56 land 0xFF); Bytes.set_uint8 m (mlen - 7) (blen lsr 48 land 0xFF); Bytes.set_uint8 m (mlen - 6) (blen lsr 40 land 0xFF); Bytes.set_uint8 m (mlen - 5) (blen lsr 32 land 0xFF); end; Bytes.set_uint8 m (mlen - 4) (blen lsr 24 land 0xFF); Bytes.set_uint8 m (mlen - 3) (blen lsr 16 land 0xFF); Bytes.set_uint8 m (mlen - 2) (blen lsr 8 land 0xFF); Bytes.set_uint8 m (mlen - 1) (blen land 0xFF); m in (* Operations on int32 *) let ( &&& ) = ( land ) in let ( lor ) = Int32.logor in let ( lxor ) = Int32.logxor in let ( land ) = Int32.logand in let ( ++ ) = Int32.add in let lnot = Int32.lognot in let sl = Int32.shift_left in let cls n x = (sl x n) lor (Int32.shift_right_logical x (32 - n)) in (* Start *) let m = sha_1_pad s in let w = Array.make 16 0l in let h0 = ref 0x67452301l in let h1 = ref 0xEFCDAB89l in let h2 = ref 0x98BADCFEl in let h3 = ref 0x10325476l in let h4 = ref 0xC3D2E1F0l in let a = ref 0l in let b = ref 0l in let c = ref 0l in let d = ref 0l in let e = ref 0l in for i = 0 to ((Bytes.length m) / 64) - 1 do (* For each block *) (* Fill w *) let base = i * 64 in for j = 0 to 15 do w.(j) <- Bytes.get_int32_be m (base + (j * 4)); done; (* Loop *) a := !h0; b := !h1; c := !h2; d := !h3; e := !h4; for t = 0 to 79 do let f, k = if t <= 19 then (!b land !c) lor ((lnot !b) land !d), 0x5A827999l else if t <= 39 then !b lxor !c lxor !d, 0x6ED9EBA1l else if t <= 59 then (!b land !c) lor (!b land !d) lor (!c land !d), 0x8F1BBCDCl else !b lxor !c lxor !d, 0xCA62C1D6l in let s = t &&& 0xF in if (t >= 16) then begin w.(s) <- cls 1 begin w.((s + 13) &&& 0xF) lxor w.((s + 8) &&& 0xF) lxor w.((s + 2) &&& 0xF) lxor w.(s) end end; let temp = (cls 5 !a) ++ f ++ !e ++ w.(s) ++ k in e := !d; d := !c; c := cls 30 !b; b := !a; a := temp; done; (* Update *) h0 := !h0 ++ !a; h1 := !h1 ++ !b; h2 := !h2 ++ !c; h3 := !h3 ++ !d; h4 := !h4 ++ !e done; let h = Bytes.create 20 in let i2s h k i = Bytes.set_int32_be h k i in i2s h 0 !h0; i2s h 4 !h1; i2s h 8 !h2; i2s h 12 !h3; i2s h 16 !h4; Bytes.unsafe_to_string h (* Uuids *) type t = string (* 16 bytes *) let make u ~version = let b6 = (version lsl 4) lor ((Bytes.get_uint8 u 6) land 0b0000_1111) in let b8 = 0b1000_0000 lor ((Bytes.get_uint8 u 8) land 0b0011_1111) in Bytes.set_uint8 u 6 b6; Bytes.set_uint8 u 8 b8; Bytes.unsafe_to_string u let make_named ~version digest ns n = let hash = Bytes.unsafe_of_string (digest (ns ^ n)) in make (Bytes.sub hash 0 16) ~version let v3 ns n = make_named ~version:3 md5 ns n let v5 ns n = make_named ~version:5 sha_1 ns n let v4 b = make (Bytes.sub b 0 16) ~version:4 let v7 ~time_ms ~rand_a ~rand_b = let u = Bytes.create 16 in Bytes.set_int64_be u 0 (Int64.shift_left time_ms 16); Bytes.set_int16_be u 6 rand_a; Bytes.set_int64_be u 8 rand_b; make u ~version:7 let v7_ns ~time_ns ~rand_b = let ns_in_ms = 1_000_000L in let sub_ms_frac_multiplier = Int64.unsigned_div Int64.minus_one ns_in_ms in let u = Bytes.create 16 in (* RFC 9562 requires we use 48 bits for a timestamp in milliseconds, and allows for 12 bits to store a sub-millisecond fraction. We get the latter by multiplying to put the fraction in a 64-bit range, then shifting into 12 bits. *) let ms = Int64.unsigned_div time_ns ns_in_ms in let ns = Int64.unsigned_rem time_ns ns_in_ms in let sub_ms_frac = Int64.shift_right_logical (Int64.mul ns sub_ms_frac_multiplier) 52 in Bytes.set_int64_be u 0 (Int64.shift_left ms 16); Bytes.set_int16_be u 6 (Int64.to_int sub_ms_frac); Bytes.set_int64_be u 8 rand_b; make u ~version:7 let v8 s = let l = String.length s in if l = 16 then make (Bytes.of_string s) ~version:8 else invalid_arg (Printf.sprintf "expected 16 bytes but found: %d" l) (* Generators *) type posix_ms_clock = unit -> int64 let v4_random rstate = let r0 = Random.State.bits64 rstate in let r1 = Random.State.bits64 rstate in let u = Bytes.create 16 in Bytes.set_int64_be u 0 r0; Bytes.set_int64_be u 8 r1; make u ~version:4 let v4_gen rstate = function () -> v4_random rstate let v7_non_monotonic_gen ~now_ms rstate = fun () -> let time_ms = now_ms () in let rand_a = Random.State.bits (* 30 bits *) rstate in let rand_b = Random.State.bits64 rstate in v7 ~time_ms ~rand_a ~rand_b let v7_monotonic_gen ~now_ms rstate = let last_ms = ref 0L in let count = ref 0 in fun () -> let time_ms = now_ms () in let rand_b = Random.State.bits64 rstate in if Int64.equal time_ms !last_ms then let rand_a = incr count; !count in if rand_a >= 4096 then None else Some (v7 ~time_ms ~rand_a ~rand_b) else (count := 0; last_ms := time_ms; Some (v7 ~time_ms ~rand_a:0 ~rand_b)) (* Constants *) let nil = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" let max = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" let ns_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" let ns_url = "\x6b\xa7\xb8\x11\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" let ns_oid = "\x6b\xa7\xb8\x12\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" let ns_X500 ="\x6b\xa7\xb8\x14\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" (* Properties *) let variant u = (String.get_uint8 u 8) lsr 4 let version u = (String.get_uint8 u 6) lsr 4 let time_ms u = let variant = variant u in if not (0x8 <= variant && variant <= 0xB && version u = 7) then None else Some (Int64.shift_right_logical (String.get_int64_be u 0) 16) (* Predicates and comparisons *) let equal = String.equal let compare = String.compare (* Standard binary format *) let to_binary_string s = s let of_binary_string ?(pos = 0) s = let len = String.length s in if pos + 16 > len then None else if pos = 0 && len = 16 then Some s else Some (String.sub s pos 16) (* Mixed endian binary format *) let mixed_swaps s = let swap b i j = let t = Bytes.get b i in Bytes.set b i (Bytes.get b j); Bytes.set b j t in let b = Bytes.of_string s in swap b 0 3; swap b 1 2; swap b 4 5; swap b 6 7; Bytes.unsafe_to_string b let to_mixed_endian_binary_string s = mixed_swaps s let of_mixed_endian_binary_string ?pos s = Option.map mixed_swaps (of_binary_string ?pos s) (* Unsafe conversions *) let unsafe_of_binary_string u = u let unsafe_to_binary_string u = u (* US-ASCII format *) let of_string ?(pos = 0) s = let len = String.length s in if pos + 36 > len || s.[pos + 8] <> '-' || s.[pos + 13] <> '-' || s.[pos + 18] <> '-' || s.[pos + 23] <> '-' then None else try let u = Bytes.create 16 in let i = ref 0 in let j = ref pos in let ihex c = let i = Char.code c in if i < 0x30 then raise Exit else if i <= 0x39 then i - 0x30 else if i < 0x41 then raise Exit else if i <= 0x46 then i - 0x37 else if i < 0x61 then raise Exit else if i <= 0x66 then i - 0x57 else raise Exit in let byte s j = Char.unsafe_chr (ihex s.[j] lsl 4 lor ihex s.[j + 1]) in while (!i < 4) do Bytes.set u !i (byte s !j); j := !j + 2; incr i done; incr j; while (!i < 6) do Bytes.set u !i (byte s !j); j := !j + 2; incr i done; incr j; while (!i < 8) do Bytes.set u !i (byte s !j); j := !j + 2; incr i done; incr j; while (!i < 10) do Bytes.set u !i (byte s !j); j := !j + 2; incr i done; incr j; while (!i < 16) do Bytes.set u !i (byte s !j); j := !j + 2; incr i done; Some (Bytes.unsafe_to_string u) with Exit -> None let to_string ?(upper = false) u = let hbase = if upper then 0x37 else 0x57 in let hex hbase i = Char.unsafe_chr (if i < 10 then 0x30 + i else hbase + i) in let s = Bytes.of_string "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" in let i = ref 0 in let j = ref 0 in let byte s i c = Bytes.set s i @@ hex hbase (c lsr 4); Bytes.set s (i + 1) @@ hex hbase (c land 0x0F) in while (!j < 4) do byte s !i (Char.code u.[!j]); i := !i + 2; incr j; done; incr i; while (!j < 6) do byte s !i (Char.code u.[!j]); i := !i + 2; incr j; done; incr i; while (!j < 8) do byte s !i (Char.code u.[!j]); i := !i + 2; incr j; done; incr i; while (!j < 10) do byte s !i (Char.code u.[!j]); i := !i + 2; incr j; done; incr i; while (!j < 16) do byte s !i (Char.code u.[!j]); i := !i + 2; incr j; done; Bytes.unsafe_to_string s (* Pretty-printing *) let pp ppf u = Format.pp_print_string ppf (to_string u) let pp' ~upper ppf u = Format.pp_print_string ppf (to_string ~upper u) (* Deprecated *) let default_seed = lazy (Random.State.make_self_init ()) type version = [ `V3 of t * string | `V4 | `V5 of t * string ] let v = function | `V4 -> v4_random (Lazy.force default_seed) | `V3 (ns, n) -> v3 ns n | `V5 (ns, n) -> v5 ns n let create = v (* deprecated *) let pp_string ?upper ppf u = Format.pp_print_string ppf (to_string ?upper u) let print = pp_string (* deprecated *) let to_bytes = to_binary_string let of_bytes = of_binary_string let to_mixed_endian_bytes = to_mixed_endian_binary_string let of_mixed_endian_bytes = of_mixed_endian_binary_string let unsafe_of_bytes = unsafe_of_binary_string let unsafe_to_bytes = unsafe_to_binary_string