diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f1b8d17..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: clojure -script: lein test -jdk: - - openjdk8 diff --git a/README.md b/README.md index 1034a53..776380e 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,14 @@ clj-uuid UUIDs (Universally Unique Identifiers) as described by [**IETF RFC-9562**](http://www.ietf.org/rfc/rfc9562.txt). -This library extends the standard Java UUID class to provide true -time based and namespace based identifier generation. -Additionally, a number of useful supporting utilities are provided to -support serialization and manipulation of these UUIDs in a simple, -efficient manner. - -The essential nature of the value RFC9562 UUIDs provide is that of an +This library extends the standard Java +UUID class to provide true v1, v6, v7 (time based), +v3/v5 (namespace based), and v8 (user customizable) +identifier generation. Additionally, a number of useful +utilities are provided to support serialization and +manipulation of these UUIDs in a simple, efficient manner. + +The essential nature of the value RFC-9562 UUIDs provide is that of an enormous namespace and a deterministic mathematical model by means of which one navigates it. UUIDs represent an extremely powerful and versatile computation technique that is often overlooked, and @@ -25,14 +26,13 @@ underutilized. In my opinion, this, in part, is due to the generally poor quality, performance, and capability of available libraries and, in part, due to a general misunderstanding in the popular consiousness of their proper use and benefit. It is my hope that this library will -serve to expand awareness, make available, and simplify use of RFC9562 -identifiers to a wider audience. +serve to expand awareness, make available, and simplify the use of standards +compliant UUIDs to a wider audience. ### The Most Recent Release With Leiningen: - [![Clojars Project](https://img.shields.io/clojars/v/danlentz/clj-uuid.svg)](https://clojars.org/danlentz/clj-uuid) @@ -93,7 +93,7 @@ In order to refer to the symbols in this library, it is recommended to ```clojure -(require '[clj-uuid :as uuid]) +(require '[clj-uuid.core :as uuid]) ``` Or include in namespace declaration: @@ -102,7 +102,7 @@ Or include in namespace declaration: ```clojure (ns foo - (:require [clj-uuid :as uuid]) + (:require [clj-uuid.core :as uuid]) ... ) @@ -112,7 +112,8 @@ Or include in namespace declaration: UUID's have a convenient literal syntax supported by the clojure reader. The tag `#uuid` denotes that the following string literal -will be read as a UUID. UUID's evaluate to themselves: +will be read as a UUID. UUID's evaluate to themselves, similarly to +Clojure keywords. ```clojure @@ -125,36 +126,37 @@ user> #uuid "e6ff478d-9492-48dd-886d-23ec4c6385ee" #### The NULL Identifier The special UUID, `#uuid "00000000-0000-0000-0000-000000000000"` is -known as the _null UUID_ or _version 0 UUID_ and can be useful for +known as the _**null** UUID_ or _version 0 UUID_ and can be useful for representing special values such as _nil_ or _null-context_. One may reference the null UUID declaratively or functionally, although it is best to pick one convention and remain consistant. When comparing UUID's the NULL UUID is considered the MININUM VALUE. - -#### The MAX Identifier - -The special UUID, `#uuid "ffffffff-ffff-ffff-ffff-ffffffffffff"` is -known as the _max UUID_ and is used similarly to the _null UUID_. When -comparing UUID's the NULL UUID is considered the MAXIMUM VALUE. - - ```clojure user> (uuid/null) ;; => #uuid "00000000-0000-0000-0000-000000000000" +user> uuid/+null+ -user> (uuid/max) +;; => #uuid "00000000-0000-0000-0000-000000000000" -;; => #uuid "ffffffff-ffff-ffff-ffff-ffffffffffff" +``` -user> uuid/+null+ +#### The MAX Identifier + +The special UUID, `#uuid "ffffffff-ffff-ffff-ffff-ffffffffffff"` is +known as the _**max** UUID_ and is used similarly to the _**null** UUID_. When +comparing UUID's the NULL UUID is considered the MAXIMUM VALUE. -;; => #uuid "00000000-0000-0000-0000-000000000000" +```clojure + +user> (uuid/max) + +;; => #uuid "ffffffff-ffff-ffff-ffff-ffffffffffff" user> uuid/+max+ @@ -164,15 +166,15 @@ user> uuid/+max+ #### v6/v1: Fast, Time Encoded Identifiers -You can make your own v1 and v6 UUID's with the functions `#'uuid/v1` -and `#'uuid/v6`. Either of these types of UUID's will be the fastest to -produce and guaranteed to be unique and thread-safe regardless of clock -precision or degree of concurrency, but each with slightly different -characteristics: +You can make your own v1 and v6 UUID's at home with the functions +`uuid/v1` and `uuid/v6`. Either of these types of UUID's will be the +fastest kind to produce and guarantee to be unique and thread-safe +regardless of clock precision or degree of concurrency, but each with +slightly different characteristics: A v6 UUID encodes both the time and a random node identifier that is reset each time the library is loaded. They are fast, lexically -(aphabetically) ordered, and index friendly. +(aphabetically) ordered, and index-friendly. A v1 UUID is similar, but may reveal both the identity of the computer that generated the UUID and the time at which it did so. Its uniqueness @@ -199,9 +201,9 @@ index-friendliness. ;; => #uuid "018a0a60-b3d4-11e4-a03e-3af93c3de9ae" ``` -Either v6 or v1 identifiers are -- _several times faster to generate -than calling the JVM's built-in static method for generating UUIDs_, -`#'java.util.UUID/randomUUID`. +Either v6 or v1 identifiers are several times faster to generate +than calling the JVM's built-in static method for generating UUIDs, +`java.util.UUID/randomUUID`. ``` @@ -271,12 +273,10 @@ user> (map uuid/get-instant (repeatedly 10 uuid/v1)) ;; #inst "2015-03-17T17:51:53.814-00:00") ``` - #### v4: Random Identifiers - V4 identifiers are generated by directly invoking the static method -`#'java.util.UUID/randomUUID` and are, in typical situations, slower to +`java.util.UUID/randomUUID` and are, in typical situations, slower to generate in addition to being non-deterministically unique. It exists primarily because it is very simple to implement and because randomly generated UUID's are hard to guess. They can be useful in that case, @@ -292,9 +292,9 @@ user> (uuid/v4) #### v7: Time Encoded Cryptographically Random Identifiers -Combining the best features of all of the above, v7 UUIDs provide time -encoding, lexical ordering, and entropy-friendly randomness, at, of -course, some additional cost to compute. +Combining some of the best features of all of the above, v7 UUIDs +provide time encoding, lexical ordering, and entropy-friendly +randomness, at, of course, some additional cost to compute. ```clojure @@ -314,16 +314,16 @@ user> (uuid/get-instant (uuid/v7)) user> (criterium.core/bench (uuid/v7)) -;; Execution time mean : 507.298388 ns +;; Execution time mean : 461.536995 ns ``` #### Lexical Comparability -Ok, you've heard me mention "lexical odering" a few times. What does +Ok, you've heard me mention "lexical ordering" a few times. What does this mean? v6 and v7 UUIDs offer identifiers that can be efficiently -ordered alphabetically, requiring no decoding, based on order of their -creation. Let's take an example: +ordered alphabetically, requiring no decoding, based on the order of +their creation. Let's take an example: ```clojure diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..a99eeaa --- /dev/null +++ b/deps.edn @@ -0,0 +1,5 @@ +{:paths ["src"] + :deps {org.clojure/clojure {:mvn/version "1.12.0"} + org.clj-commons/primitive-math {:mvn/version "1.0.1"}} + :aliases + {:test {:extra-paths ["test"]}}} diff --git a/project.clj b/project.clj index 7a771a1..5c91c2b 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject danlentz/clj-uuid "0.2.0" +(defproject danlentz/clj-uuid "0.2.1-SNAPSHOT" :description "A Clojure library for generation and utilization of UUIDs (Universally Unique Identifiers) as described by RFC-9562. This library extends the standard Java diff --git a/src/clj_uuid.clj b/src/clj_uuid.clj index 9a6a82a..7d6b02f 100644 --- a/src/clj_uuid.clj +++ b/src/clj_uuid.clj @@ -1,873 +1,74 @@ (ns clj-uuid + "DEPRECATED: use clj-uuid.core" + {:deprecated "0.2.1" + :superseded-by "clj-uuid.core" + :no-doc true} (:refer-clojure :exclude [== uuid? max < > =]) - (:require [clojure.core :as clojure] - [clj-uuid - [constants :refer :all] - [util :as util] - [bitmop :as bitmop] - [clock :as clock] - [node :as node] - [random :as random]]) - (:import [java.io ByteArrayOutputStream - ObjectOutputStream] - [java.lang IllegalArgumentException] - [java.net URI URL] - [java.nio ByteBuffer] - [java.security MessageDigest] - [java.util UUID Date])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Leach-Salz UUID Representation [RFC4122:4.1.2 "LAYOUT AND BYTE ORDER"] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; The string representation of A Leach-Salz UUID has the format: -;; -;; clock-seq-and-reserved -;; time-mid | clock-seq-low -;; | | | -;; 6ba7b810-9dad-11d1-80b4-00c04fd430c8 -;; | | | -;; ` time-low | ` node -;; ` time-high-and-version -;; -;; -;; Each field is treated as integer and has its value printed as a zero-filled -;; hexadecimal digit string with the most significant digit first (unsigned, -;; big-endian). -;; -;; 0 1 2 3 -;; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | %uuid_time-low | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | %uuid_time-mid | %uuid_time-high-and-version | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; |clk-seq-hi-res | clock-seq-low | %uuid_node (0-1) | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | %uuid_node (2-5) | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; -;; -;; The following table enumerates a slot/type/value correspondence: -;; -;; SLOT SIZE TYPE BYTE-ARRAY -;; ---------------------------------------------------------------------- -;; time-low 4 ub32 [ ] -;; time-mid 2 ub16 [ ] -;; time-high 2 ub16 [ ] -;; clock-high 1 ub8 [] -;; clock-low 1 ub8 [] -;; node 6 ub48 [ ] -;; -;; Or, as 8-bit bytes mapping into 128-bit unsigned integer values: -;; -;; (0 7) (8 15) (16 23) (24 31) ;; time-low -;; (32 39) (40 47) ;; time-mid -;; (48 55) (56 63) ;; time-high-and-version -;; -;; (64 71) ;; clock-seq-and-reserved -;; (72 79) ;; clock-seq-low -;; (80 87) (88 95) (96 103) ;; -;; (104 111) (112 119) (120 127) ;; node -;; -;; This has been updated with additional layouts. See RFC9562:5.7 and -;; the description of v7 UUIDs, below. - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; UUID Variant [RFC9562:4.1] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; The variant indicates the layout of the UUID. The UUID specification -;; covers one particular variant. Other variants are reserved or exist -;; for backward compatibility reasons (e.g., for values assigned before -;; the UUID specification was produced). An example of a UUID that is a -;; different variant is the null UUID, which is a UUID that has all 128 -;; bits set to zero. -;; -;; In the canonical representation: -;; -;; xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx -;; -;; the most significant bits of N indicate the variant (depending on the -;; variant one, two, or three bits are used). The principal variants covered -;; by the RFC9562 are indicated by the two most significant bits of N being 1 0 -;; (i.e., the hexadecimal N will always be 8, 9, A, or B). As seen below, -;; there are two additional variants, currently used for special "sentinal" -;; UUID's as defined below. - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; UUID Version [RFC9562:4.2] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; The Leach-Salz UUID variant has five defined versions. In the canonical -;; representation: -;; -;; xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx -;; -;; the four bits of M indicates the UUID version (i.e., the hexadecimal -;; digit M will be either 1, 2, 3, 4, 5, 6, 7, or 8). - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The NULL (variant 0) UUID [RFC9562:5.9] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def ^:const +null+ - "The NULL UUID is a special form of sentinel UUID that is specified to have - all 128 bits set to zero." - #uuid "00000000-0000-0000-0000-000000000000") - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The MAX (variant 7) UUID [RFC9562:5.10] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def ^:const +max+ - "The MAX UUID is a special form of sentinel UUID that is specified to have - all 128 bits set to one." - #uuid "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Well-Known UUIDs [RFC4122:Appendix-C "SOME NAMESPACE IDs"] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; The following UUID's are the canonical top-level namespace identifiers -;; defined in RFC9562 Appendix C. - -(def ^:const +namespace-dns+ #uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8") -(def ^:const +namespace-url+ #uuid "6ba7b811-9dad-11d1-80b4-00c04fd430c8") -(def ^:const +namespace-oid+ #uuid "6ba7b812-9dad-11d1-80b4-00c04fd430c8") -(def ^:const +namespace-x500+ #uuid "6ba7b814-9dad-11d1-80b4-00c04fd430c8") - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Monotonic Clock (guaranteed always increasing value for time) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn monotonic-time - "Return a monotonic timestamp (guaranteed always increasing) based on - the number of 100-nanosecond intervals elapsed since the adoption of - the Gregorian calendar in the West, 12:00am Friday October 15, 1582 UTC." - [] - (clock/monotonic-time)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; UUID Protocols -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defprotocol UUIDNameBytes - "A mechanism intended for user-level extension that defines the - decoding rules for the local-part representation of arbitrary - Clojure / Java Objects when used for computing namespaced - identifiers." - - (as-byte-array [x] - "Extract a byte serialization that represents the 'name' of x, - typically unique within a given namespace.")) - -(defprotocol UUIDable - "A UUIDable object directly represents a UUID. Examples of things which - might be conceptually 'uuidable' include string representation of a - UUID in canonical hex format, or an appropriate URN URI." - - (as-uuid ^java.util.UUID [x] - "Coerce the value 'x' to a UUID.") - - (uuidable? [x] - "Return 'true' if 'x' can be coerced to UUID.")) - -(defprotocol UUIDRfc9562 - "A protocol that abstracts an unique identifier as described by - IETF RFC9562 . A UUID - represents a 128-bit value, however there are variant encoding - layouts used to assign and interpret information encoded in - those bits. This is a protocol for _variant 2_ (*Leach-Salz*) - UUID's." - - (hash-code [uuid] - "Return a suitable 64-bit hash value for `uuid`. Extend with - specialized hash computation.") - - (null? [uuid] - "Return `true` only if `uuid` has all 128 bits set to zero and is - therefore equal to the null UUID, 00000000-0000-0000-0000-000000000000.") - - (max? [uuid] - "Return `true` only if `uuid` has all 128 bits set and is - therefore equal to the maximum UUID, ffffffff-ffff-ffff-ffff-ffffffffffff.") - - (uuid? [x] - "Return `true` if `x` implements an RFC9562 unique identifier.") - - (uuid= [x y] - "Directly compare two UUID's for = relation based on the equality - semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. - See: `clj-uuid/=`") - - (uuid< [x y] - "Directly compare two UUID's for < relation based on the ordinality - semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. - See: `clj-uuid/<`") - - (uuid> [x y] - "Directly compare two UUID's for > relation based on the ordinality - semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. - See: `clj-uuid/>`") - - (get-word-high [uuid] - "Return the most significant 64 bits of UUID's 128 bit value.") - - (get-word-low [uuid] - "Return the least significant 64 bits of UUID's 128 bit value.") - - (get-version [uuid] - "Return the version number associated with this UUID. The version - field contains a value which describes the nature of the UUID. There - are five versions of Leach-Salz UUID, plus the null and max UUIDs: - - 0x0 Null - 0x1 Time based - 0x2 DCE security with POSIX UID - 0x3 Namespaced, deterministic (MD5 Digest) - 0x4 Cryptographic random - 0x5 Namespaced, deterministic (SHA1 Digest) - 0x6 Time based, lexically ordered - 0x7 POSIX Time based, lexically ordered, cryptographically secure - 0x8 User Customizable - 0xF Max - - In the canonical representation, xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx, - the four bits of M indicate the UUID version (i.e., the hexadecimal M - will be either 1, 2, 3, 4, 5, 6, 7, or 8).") - - (get-variant [uuid] - "Return the variant number associated with this UUID. The variant field - contains a value which identifies the layout of the UUID. The bit-layout - implemented by this protocol supports UUID's with a variant value of 0x2, - which indicates Leach-Salz layout. Defined UUID variant values are: - - 0x0 Null - 0x2 Leach-Salz - 0x6 Microsoft - 0x7 Max - - In the canonical representation, xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx, - the most significant bits of N indicate the variant (depending on the - variant one, two, or three bits are used). The variant covered by RFC9562 - is indicated by the two most significant bits of N being 1 0 (i.e., the - hexadecimal N will always be 8, 9, A, or B).") - - (get-time-low [uuid] - "Return the 32 bit unsigned value that represents the `time-low` field - of the `timestamp` associated with this UUID.") - - (get-time-mid [uuid] - "Return the 16 bit unsigned value that represents the `time-mid` field - of the `timestamp` assocaited with this UUID.") - - (get-time-high [uuid] - "Return the 16 bit unsigned value that represents the `time-high` field - of the `timestamp` multiplexed with the `version` of this UUID.") - - (get-clk-high [uuid] - "Return the 8 bit unsigned value that represents the most significant - byte of the `clk-seq` multiplexed with the `variant` of this UUID.") - - (get-clk-low [uuid] - "Return the 8 bit unsigned value that represents the least significant - byte of the `clk-seq` associated with this UUID.") - - (get-clk-seq [uuid] - "Return the clock-sequence number associated with this UUID. For time-based - (v1, v6) UUID's the 'clock-sequence' value is a somewhat counter-intuitively - named seed-value that is used to reduce the potential that duplicate UUID's - might be generated under unusual situations, such as if the system hardware - clock is set backward in time or if, despite all efforts otherwise, a - duplicate node-id happens to be generated. This value is initialized to - a random 16-bit number once per lifetime of the system. For - non-gregorian-time-based (v3, v4, v5, v7, v8, squuid) UUID's, always - returns `nil`.") - - (get-node-id [uuid] - "Return the 48 bit unsigned value that represents the spatially unique - node identifier associated with this UUID.") - - (get-timestamp [uuid] - "Return the time of UUID creation. For Gregorian time-based (v1, - v6) UUID's, this is 60 bit unsigned value that represents a - temporally unique timestamp associated with this UUID. The result - encodes the number of 100 nanosecond intervals since the adoption of - the Gregorian calendar. For v7 UUID's this encodes the more common - unix time in milliseconds since midnight, January 1, 1970 UTC. For - non-time-based (v3, v4, v5, v8, squuid) UUID's, always returns - `nil`.") - - (get-instant ^java.util.Date [uuid] - "For time-based (v1, v6, v7) UUID's, return a java.util.Date - object that represents the system time at which this UUID was - generated. NOTE: the returned value may not necessarily be - temporally unique. For non-time-based - (v3, v4, v5, v8, squuid) UUID's, always returns `nil`.") - - (get-unix-time [uuid] - "For time-based (v1, v6, v7) UUIDs return the timestamp portion in - aproximately milliseconds since the Unix epoch 1970-01-01T00:00:00.000Z. - For non-time-based (v3, v4, v5, v8, squuid) UUID's, always returns `nil`.") - - (to-byte-array [uuid] - "Return an array of 16 bytes that represents `uuid` as a decomposed - octet serialization encoded in most-significant-byte first order.") - - (to-string ^String [uuid] - "Return a String object that represents `uuid` in the canonical - 36 character hex-string format: - - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") - - (to-hex-string ^String [uuid] - "Return a String object that represents `uuid` as the 32 hexadecimal - characters directly encodong the UUID's 128 bit value: - - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") - - (to-urn-string ^String [uuid] - "Return a String object that represents `uuid` as a the string - serialization of the URN URI based on the canonical 36 character - hex-string representation: - - urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") - - (to-uri ^java.net.URI [uuid] - "Return the unique URN URI associated with this UUID.")) - -;; For backwards compatibility - -(def UUIDRfc4122 UUIDRfc9562) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; RFC9562 Unique Identifier extended java.util.UUID -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(extend-type UUID - - UUIDable - - (as-uuid [u] u) - (uuidable? [_] true) - - UUIDRfc9562 - - (uuid? ^boolean [_] true) - - (uuid= ^boolean [^UUID x ^UUID y] - (.equals x y)) - - (uuid< ^boolean [^UUID x ^UUID y] - (let [xh (.getMostSignificantBits x) - yh (.getMostSignificantBits y)] - (or (clojure/< xh yh) - (and (clojure/= xh yh) (clojure/< (.getLeastSignificantBits x) - (.getLeastSignificantBits y)))))) - - (uuid> ^boolean [^UUID x ^UUID y] - (let [xh (.getMostSignificantBits x) - yh (.getMostSignificantBits y)] - (or (clojure/> xh yh) - (and (clojure/= xh yh) (clojure/> (.getLeastSignificantBits x) - (.getLeastSignificantBits y)))))) - - (get-word-high ^long [uuid] - (.getMostSignificantBits uuid)) - - (get-word-low ^long [uuid] - (.getLeastSignificantBits uuid)) - - (null? ^boolean [uuid] - (clojure/= 0 (.getMostSignificantBits uuid) (.getLeastSignificantBits uuid))) - - (max? ^boolean [uuid] - (uuid= uuid +max+)) - - (to-byte-array ^bytes [uuid] - (let [arr (byte-array 16)] - (bitmop/long->bytes (.getMostSignificantBits uuid) arr 0) - (bitmop/long->bytes (.getLeastSignificantBits uuid) arr 8) - arr)) - - (hash-code ^long [uuid] - (long (.hashCode uuid))) - - (get-version ^int [uuid] - (.version uuid)) - - (get-variant ^int [uuid] - (.variant uuid)) - - (to-string [uuid] - (.toString uuid)) - - (to-urn-string [uuid] - (str "urn:uuid:" (.toString uuid))) - - (to-hex-string [uuid] - (str (bitmop/hex (get-word-high uuid)) (bitmop/hex (get-word-low uuid)))) - - (to-uri [uuid] - (URI/create (to-urn-string uuid))) - - (get-time-low ^long [uuid] - (let [msb (.getMostSignificantBits uuid)] - (if (clojure/= 6 (get-version uuid)) - (bitmop/ldb #=(bitmop/mask 16 0) msb) - (bitmop/ldb #=(bitmop/mask 32 0) (bit-shift-right msb 32))))) - - (get-time-mid ^long [uuid] - (bitmop/ldb #=(bitmop/mask 16 16) - (.getMostSignificantBits uuid))) - - (get-time-high ^long [uuid] - (let [msb (.getMostSignificantBits uuid)] - (if (clojure/= 6 (get-version uuid)) - (bitmop/ldb #=(bitmop/mask 32 0) (bit-shift-right msb 32)) - (bitmop/ldb #=(bitmop/mask 16 0) msb)))) - - (get-clk-low ^long [uuid] - (bitmop/ldb #=(bitmop/mask 8 0) - (bit-shift-right (.getLeastSignificantBits uuid) 56))) - - (get-clk-high ^long [uuid] - (bitmop/ldb #=(bitmop/mask 8 48) - (.getLeastSignificantBits uuid))) - - (get-clk-seq ^short [uuid] - (when (#{1 6} (.version uuid)) - (.clockSequence uuid))) - - (get-node-id ^long [uuid] - (bitmop/ldb #=(bitmop/mask 48 0) - (.getLeastSignificantBits uuid))) - - (get-timestamp ^long [uuid] - (case (.version uuid) - 1 (.timestamp uuid) - 6 (bit-or (bitmop/ldb #=(bitmop/mask 12 0) - (.getMostSignificantBits uuid)) - (bit-shift-left (get-time-mid uuid) 12) - (bit-shift-left (get-time-high uuid) 28)) - 7 (bitmop/ldb #=(bitmop/mask 48 16) (.getMostSignificantBits uuid)) - nil)) - - (get-unix-time ^long [uuid] - (case (.version uuid) - (1 6) (clock/posix-time (get-timestamp uuid)) - 7 (get-timestamp uuid) - nil)) - - (get-instant [uuid] - (when-let [ts (get-unix-time uuid)] - (Date. (long ts)))) - - UUIDNameBytes - - (as-byte-array - ^bytes - [this] - (to-byte-array this))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; V0 UUID Constructor [RFC9562:5.9] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn null - "Generates the v0 (null) UUID, 00000000-0000-0000-0000-000000000000." - ^java.util.UUID - [] - +null+) - -(defn v0 - "Generates the v0 (null) UUID, 00000000-0000-0000-0000-000000000000." - ^java.util.UUID - [] - +null+) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The MAX UUID Constructor [RFC9562:5.10] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn max - "Generates the v15 (maximum) UUID, ffffffff-ffff-ffff-ffff-ffffffffffff." - ^java.util.UUID - [] - +max+) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; V1, V6 (time-coded) UUID Constructors [RFC9562:5.1, 5.6] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; Concatenate the UUID version with a unique per-node identifier (the -;; MAC address of the computer that is generating the UUID, or securely -;; generated once-per-session random identifier) and with a monotonic -;; timestamp based on the number of 100-nanosecond intervals since the -;; adoption of the Gregorian calendar in the West, 12:00am Friday -;; October 15, 1582 UTC. - -(defn v1 - "Generate a v1 (time-based) unique identifier, guaranteed to be unique - and thread-safe regardless of clock precision or degree of concurrency. - Creation of v1 UUID's does not require any call to a cryptographic - generator and can be accomplished much more efficiently than v3, v4, v5, v7, - or squuid's. A v1 UUID reveals both the identity of the computer that - generated the UUID and the time at which it did so. Its uniqueness across - computers is guaranteed as long as MAC addresses are not duplicated." - ^java.util.UUID - [] - (let [ts (clock/monotonic-time) - time-low (bitmop/ldb #=(bitmop/mask 32 0) ts) - time-mid (bitmop/ldb #=(bitmop/mask 16 32) ts) - time-high (bitmop/dpb #=(bitmop/mask 4 12) - (bitmop/ldb #=(bitmop/mask 12 48) ts) 0x1) - msb (bit-or time-high - (bit-shift-left time-low 32) - (bit-shift-left time-mid 16))] - (UUID. msb (node/+v1-lsb+)))) - -(defn v6 - "Generate a v6 (time-based), LEXICALLY SORTABLE, unique identifier, - v6 is a field-compatible version of v1, reordered for improved DB - locality. Creation of v6 UUID's does not require any call to a - cryptographic generator and can be accomplished much more efficiently - than v3, v4, v5, v7, or squuid's. A v6 UUID uses a cryptographically - secure, hard to guess random node id. It DOES NOT reveal the identity - of the computer on which it was created." - ^java.util.UUID - [] - (let [ts (clock/monotonic-time) - time-high (bitmop/ldb #=(bitmop/mask 32 28) ts) - time-mid (bitmop/ldb #=(bitmop/mask 16 12) ts) - time-low (bitmop/dpb #=(bitmop/mask 4 12) - (bitmop/ldb #=(bitmop/mask 12 0) ts) 0x6) - msb (bit-or time-low - (bit-shift-left time-mid 16) - (bit-shift-left time-high 32))] - (UUID. msb (node/+v6-lsb+)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; V7 (crypto secure, posix time-coded) UUID Constructor [RFC9562:5.7] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; 0 1 2 3 -;; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | unix_ts_ms | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | unix_ts_ms | ver | rand_a | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; |var| rand_b | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -;; | rand_b | -;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -;; The following table enumerates a slot/type/value correspondence: -;; -;; SLOT SIZE Description -;; ---------------------------------------------------------------------- -;; unix_ts_ms 6 bytes POSIX millis timestamp -;; ver 4 bits version, set to 0b0111 (7) -;; rand_a 12 bits randomly initialized subcounter -;; var 2 bits variant, set to 0b10 (2) -;; rand_b 62 bits cryptographically secure pseudorandom data - -(defn v7 - "Generate a v7 unix time-based, LEXICALLY SORTABLE UUID with monotonic - counter and cryptographically secure random portion and POSIX time encoding. - As such, creation of v7 UUIDs may be significantly slower, but have improved - entropy chararacteristics compared to v1 or v6 UUIDs." - ^java.util.UUID - [] - (let [[t counter] (clock/monotonic-unix-time-and-random-counter) - time (bitmop/ldb #=(bitmop/mask 48 0) t) - ver-and-counter (bitmop/dpb #=(bitmop/mask 4 12) counter 0x7) - msb (bit-or ver-and-counter (bit-shift-left time 16)) - lsb (bitmop/dpb #=(bitmop/mask 2 62) (random/long) 0x2)] - (UUID. msb lsb))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; V4 (random) UUID Constructor [RFC9562:5.4] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn v4 - "Generate a v4 (random) UUID. Uses default JVM implementation. If two - arguments, lsb and msb (both long) are provided, then construct a valid, - properly formatted v4 UUID based on those values. So, for example the - following UUID, created from all zero bits, is indeed distinct from the - null UUID: - - (v4) - => #uuid \"dcf0035f-ea29-4d1c-b52e-4ea499c6323e\" - - (v4 0 0) - => #uuid \"00000000-0000-4000-8000-000000000000\" - - (null) - => #uuid \"00000000-0000-0000-0000-000000000000\"" - (^java.util.UUID [] - (UUID/randomUUID)) - (^java.util.UUID [msb lsb] - (UUID. - (bitmop/dpb #=(bitmop/mask 4 12) msb 0x4) - (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; v8 (custom) UUID Constructor [RFC9562:5.8: UUID Version 8];; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn v8 - "Generate a v8 custom UUID with up to 122 bits of user data." - ^java.util.UUID - [^long msb ^long lsb] - (UUID. - (bitmop/dpb #=(bitmop/mask 4 12) msb 0x8) - (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; SQUUID (sequential) UUID Constructor -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn squuid - "Generate a SQUUID (sequential, random) unique identifier. SQUUID's - are a nonstandard variation on v4 (random) UUIDs that have the - desirable property that they increase sequentially over time as well - as encode retrievably the posix time at which they were generated. - Splits and reassembles a v4 UUID to merge current POSIX - time (seconds since 12:00am January 1, 1970 UTC) with the most - significant 32 bits of the UUID." - ^java.util.UUID - [] - (let [uuid (v4) - secs (clock/posix-time) - lsb (get-word-low uuid) - msb (get-word-high uuid) - timed-msb (bit-or (bit-shift-left secs 32) - (bit-and +ub32-mask+ msb))] - (UUID. timed-msb lsb))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; "Local-Part" Representation -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; The following represent a default set of local-part encoding rules. As a -;; default, a plain byte-array will be passed through unchanged and a -;; generic java.lang.Object is represented by the bytes of its serialization. -;; Strings are represented using UTF8 encoding. URL's are digested as the -;; UTF bytes of their string representation. - -(def ^:private ByteArray (class (byte-array 0))) - -(extend-protocol UUIDNameBytes - - java.lang.Object - (as-byte-array ^bytes [this] - (if (instance? ByteArray this) - this - (let [baos (ByteArrayOutputStream.) - oos (ObjectOutputStream. baos)] - (.writeObject oos this) - (.close oos) - (.toByteArray baos)))) - - java.lang.String - (as-byte-array ^bytes [this] - (util/compile-if (java6?) - (.getBytes this) - (.getBytes this java.nio.charset.StandardCharsets/UTF_8))) - - java.net.URL - (as-byte-array ^bytes [this] - (as-byte-array (.toString this))) - - nil - (as-byte-array [x] - (throw (IllegalArgumentException. (format "%s cannot be converted to byte array." x))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Digest Instance -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- make-digest - ^java.security.MessageDigest - [^String designator] - (MessageDigest/getInstance designator)) - -(defn- digest-bytes - ^bytes - [^String kind ^bytes ns-bytes ^bytes local-bytes] - (let [m (make-digest kind)] - (.update m ns-bytes) - (.digest m local-bytes))) - -(defn- build-digested-uuid - ^java.util.UUID - [^long version ^bytes arr] - {:pre [(or (clojure/= version 3) (clojure/= version 5))]} - (let [msb (bitmop/bytes->long arr 0) - lsb (bitmop/bytes->long arr 8)] - (UUID. - (bitmop/dpb #=(bitmop/mask 4 12) msb version) - (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Namespaced UUIDs [RFC9562:5.3, 5.5] ;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn v3 - "Generate a v3 (name based, MD5 hash) UUID. 'context' must be UUIDable. - v3 identifiers are intended for generating UUID's from names that are - drawn from, and unique within, some namespace. The concept of name and - namespace should be broadly construed, and not limited to textual names. - The requiremens for a v3 UUID are as follows: - - * v3 UUID's generated at different times from the same name in the same - namespace MUST be equal. - - * v3 UUID's generated from two different names in the same namespace - SHOULD be distinct with a high degree of certainty. - - * v3 UUID's generated from the same name in two different namespaces - SHOULD be distinct with a high degree of certainty. - - * If two v3 UUID's are equal, then there is a high degree of certainty - that they were generated from the same name in the same namespace." - ^java.util.UUID - [context local-part] - (build-digested-uuid 3 - (digest-bytes +md5+ - (to-byte-array (as-uuid context)) - (as-byte-array local-part)))) - - -(defn v5 - "Generate a v5 (name based, SHA1 hash) UUID. 'context' must be UUIDable. - v5 identifiers are intended for generating UUID's from names that are - drawn from, and unique within, some namespace. The concept of name and - namespace should be broadly construed, and not limited to textual names. - The requiremens for a v5 UUID are as follows: - - * v5 UUID's generated at different times from the same name in the same - namespace MUST be equal. - - * v5 UUID's generated from two different names in the same namespace - SHOULD be distinct with a high degree of certainty. - - * v5 UUID's generated from the same name in two different namespaces - SHOULD be distinct with a high degree of certainty. - - * If two v5 UUID's are equal, then there is a high degree of certainty - that they were generated from the same name in the same namespace." - ^java.util.UUID - [context local-part] - (build-digested-uuid 5 - (digest-bytes +sha1+ - (to-byte-array (as-uuid context)) - (as-byte-array local-part)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Predicates -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- compare-many - [f x y more] - (if (f x y) - (if (next more) - (recur f y (first more) (next more)) - (f y (first more))) - false)) - -(defn = - "Directly compare two or more UUIDs for = relation based on the - equality semantics defined by [RFC4122:3 RULES FOR LEXICAL - EQUIVALENCE]." - ([_] true) - ([x y] - (uuid= x y)) - ([x y & more] - (compare-many uuid= x y more))) - -(defn > - "Directly compare two or more UUIDs for > relation based on the - ordinality semantics defined by [RFC4122:3 RULES FOR LEXICAL - EQUIVALENCE]." - ([_] true) - ([x y] - (uuid> x y)) - ([x y & more] - (compare-many uuid> x y more))) - -(defn < - "Directly compare two or more UUIDs for < relation based on the - ordinality semantics defined by [RFC4122:3 RULES FOR LEXICAL - EQUIVALENCE]." - ([_] true) - ([x y] - (uuid< x y)) - ([x y & more] - (compare-many uuid< x y more))) - -(defn uuid-string? [str] - (and (string? str) - (some? (re-matches uuid-regex str)))) - -(defn uuid-urn-string? [str] - (and (string? str) - (some? (re-matches urn-regex str)))) - -(defn uuid-vec? [v] - (and (clojure/= (count v) 16) - (every? #(and (integer? %) (>= -128 % 127)) v))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; UUID Polymorphism -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- str->uuid [s] - (cond - (uuid-string? s) (UUID/fromString s) - (uuid-urn-string? s) (UUID/fromString (subs s 9)) - :else (throw (IllegalArgumentException. (format "Invalid UUID: %s" s))))) - -(extend-protocol UUIDRfc9562 - Object - (uuid? [x] false) - - nil - (uuid? [_] false)) - -(extend-protocol UUIDable - (Class/forName "[B") ; byte array - (as-uuid [^bytes ba] - (let [bb (ByteBuffer/wrap ba)] - (UUID. (.getLong bb) (.getLong bb)))) - (uuidable? [^bytes ba] - (clojure/= 16 (alength ^bytes ba))) - - String - (uuidable? ^boolean [s] - (or - (uuid-string? s) - (uuid-urn-string? s))) - (as-uuid [s] - (str->uuid s)) - - URI - (uuidable? ^boolean [u] - (uuid-urn-string? (str u))) - (as-uuid [u] - (str->uuid (str u))) - - Object - (uuidable? ^boolean [_] - false) - (as-uuid [x] - (throw (IllegalArgumentException. (format "%s Cannot be coerced to UUID." x)))) - - nil - (as-uuid [x] - (throw (IllegalArgumentException. (format "%s cannot be coerced to UUID." x)))) - (uuidable? ^boolean [_] false)) + (:require [clj-uuid.core :as uuid])) + +(def UUIDNameBytes uuid/UUIDNameBytes) +(def UUIDable uuid/UUIDable) +(def UUIDRfc4122 uuid/UUIDRfc4122) +(def UUIDRfc9562 uuid/UUIDRfc9562) + +(def monotonic-time uuid/monotonic-time) +(def v0 uuid/v0) +(def v1 uuid/v1) +(def v3 uuid/v3) +(def v4 uuid/v4) +(def v5 uuid/v5) +(def v6 uuid/v6) +(def v7 uuid/v7) +(def v8 uuid/v8) +(def squuid uuid/squuid) + +(def hash-code uuid/hash-code) +(def = uuid/=) +(def < uuid/<) +(def > uuid/>) +(def uuid= uuid/uuid=) +(def uuid> uuid/uuid>) +(def uuid< uuid/uuid<) + +(def get-instant uuid/get-instant) +(def get-unix-time uuid/get-unix-time) +(def get-timestamp uuid/get-timestamp) + +(def to-hex-string uuid/to-hex-string) +(def to-byte-array uuid/to-byte-array) +(def to-urn-string uuid/to-urn-string) +(def to-string uuid/to-string) +(def to-uri uuid/to-uri) +(def as-uuid uuid/as-uuid) +(def as-byte-array uuid/as-byte-array) + +(def get-word-low uuid/get-word-low) +(def get-clk-seq uuid/get-clk-seq) +(def get-clk-low uuid/get-clk-low) +(def get-node-id uuid/get-node-id) +(def get-word-high uuid/get-word-high) +(def get-clk-high uuid/get-clk-high) +(def get-time-low uuid/get-time-low) +(def get-version uuid/get-version) +(def get-time-high uuid/get-time-high) +(def get-time-mid uuid/get-time-mid) +(def get-variant uuid/get-variant) + +(def uuid? uuid/uuid?) +(def uuid-vec? uuid/uuid-vec?) +(def uuid-string? uuid/uuid-string?) +(def uuid-urn-string? uuid/uuid-urn-string?) +(def uuidable? uuid/uuidable?) + +(def +max+ uuid/+max+) +(def max uuid/max) +(def max? uuid/max?) + +(def +null+ uuid/+null+) +(def null uuid/null) +(def null? uuid/null?) + +(def +namespace-oid+ uuid/+namespace-oid+) +(def +namespace-url+ uuid/+namespace-url+) +(def +namespace-x500+ uuid/+namespace-x500+) +(def +namespace-dns+ uuid/+namespace-dns+) diff --git a/src/clj_uuid/clock.clj b/src/clj_uuid/clock.clj index 89b70cc..cf0faf0 100644 --- a/src/clj_uuid/clock.clj +++ b/src/clj_uuid/clock.clj @@ -98,7 +98,7 @@ (def ^:const +random-counter-resolution+ 0xfff) -(let [-state- (atom (->State 0 0))] +(let [-state- (atom (->State (random/ten-bits) 0))] (defn monotonic-unix-time-and-random-counter "Generate guaranteed monotonically increasing number pairs based on POSIX time and a randomly seeded subcounter" @@ -110,7 +110,7 @@ (let [time-now (System/currentTimeMillis)] (cond (< (.millis current-state) time-now) - (->State (random/eight-bits) time-now) + (->State (random/ten-bits) time-now) (> (.millis current-state) time-now) (recur) diff --git a/src/clj_uuid/core.clj b/src/clj_uuid/core.clj new file mode 100644 index 0000000..60e479d --- /dev/null +++ b/src/clj_uuid/core.clj @@ -0,0 +1,873 @@ +(ns clj-uuid.core + (:refer-clojure :exclude [== uuid? max < > =]) + (:require [clojure.core :as clojure] + [clj-uuid + [constants :refer :all] + [util :as util] + [bitmop :as bitmop] + [clock :as clock] + [node :as node] + [random :as random]]) + (:import [java.io ByteArrayOutputStream + ObjectOutputStream] + [java.lang IllegalArgumentException] + [java.net URI URL] + [java.nio ByteBuffer] + [java.security MessageDigest] + [java.util UUID Date])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Leach-Salz UUID Representation [RFC4122:4.1.2 "LAYOUT AND BYTE ORDER"] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; The string representation of A Leach-Salz UUID has the format: +;; +;; clock-seq-and-reserved +;; time-mid | clock-seq-low +;; | | | +;; 6ba7b810-9dad-11d1-80b4-00c04fd430c8 +;; | | | +;; ` time-low | ` node +;; ` time-high-and-version +;; +;; +;; Each field is treated as integer and has its value printed as a zero-filled +;; hexadecimal digit string with the most significant digit first (unsigned, +;; big-endian). +;; +;; 0 1 2 3 +;; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | %uuid_time-low | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | %uuid_time-mid | %uuid_time-high-and-version | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; |clk-seq-hi-res | clock-seq-low | %uuid_node (0-1) | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | %uuid_node (2-5) | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; +;; +;; The following table enumerates a slot/type/value correspondence: +;; +;; SLOT SIZE TYPE BYTE-ARRAY +;; ---------------------------------------------------------------------- +;; time-low 4 ub32 [ ] +;; time-mid 2 ub16 [ ] +;; time-high 2 ub16 [ ] +;; clock-high 1 ub8 [] +;; clock-low 1 ub8 [] +;; node 6 ub48 [ ] +;; +;; Or, as 8-bit bytes mapping into 128-bit unsigned integer values: +;; +;; (0 7) (8 15) (16 23) (24 31) ;; time-low +;; (32 39) (40 47) ;; time-mid +;; (48 55) (56 63) ;; time-high-and-version +;; +;; (64 71) ;; clock-seq-and-reserved +;; (72 79) ;; clock-seq-low +;; (80 87) (88 95) (96 103) ;; +;; (104 111) (112 119) (120 127) ;; node +;; +;; This has been updated with additional layouts. See RFC9562:5.7 and +;; the description of v7 UUIDs, below. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; UUID Variant [RFC9562:4.1] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; The variant indicates the layout of the UUID. The UUID specification +;; covers one particular variant. Other variants are reserved or exist +;; for backward compatibility reasons (e.g., for values assigned before +;; the UUID specification was produced). An example of a UUID that is a +;; different variant is the null UUID, which is a UUID that has all 128 +;; bits set to zero. +;; +;; In the canonical representation: +;; +;; xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx +;; +;; the most significant bits of N indicate the variant (depending on the +;; variant one, two, or three bits are used). The principal variants covered +;; by the RFC9562 are indicated by the two most significant bits of N being 1 0 +;; (i.e., the hexadecimal N will always be 8, 9, A, or B). As seen below, +;; there are two additional variants, currently used for special "sentinal" +;; UUID's as defined below. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; UUID Version [RFC9562:4.2] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; The Leach-Salz UUID variant has five defined versions. In the canonical +;; representation: +;; +;; xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx +;; +;; the four bits of M indicates the UUID version (i.e., the hexadecimal +;; digit M will be either 1, 2, 3, 4, 5, 6, 7, or 8). + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The NULL (variant 0) UUID [RFC9562:5.9] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:const +null+ + "The NULL UUID is a special form of sentinel UUID that is specified to have + all 128 bits set to zero." + #uuid "00000000-0000-0000-0000-000000000000") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The MAX (variant 7) UUID [RFC9562:5.10] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:const +max+ + "The MAX UUID is a special form of sentinel UUID that is specified to have + all 128 bits set to one." + #uuid "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Well-Known UUIDs [RFC4122:Appendix-C "SOME NAMESPACE IDs"] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The following UUID's are the canonical top-level namespace identifiers +;; defined in RFC9562 Appendix C. + +(def ^:const +namespace-dns+ #uuid "6ba7b810-9dad-11d1-80b4-00c04fd430c8") +(def ^:const +namespace-url+ #uuid "6ba7b811-9dad-11d1-80b4-00c04fd430c8") +(def ^:const +namespace-oid+ #uuid "6ba7b812-9dad-11d1-80b4-00c04fd430c8") +(def ^:const +namespace-x500+ #uuid "6ba7b814-9dad-11d1-80b4-00c04fd430c8") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Monotonic Clock (guaranteed always increasing value for time) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn monotonic-time + "Return a monotonic timestamp (guaranteed always increasing) based on + the number of 100-nanosecond intervals elapsed since the adoption of + the Gregorian calendar in the West, 12:00am Friday October 15, 1582 UTC." + [] + (clock/monotonic-time)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; UUID Protocols +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defprotocol UUIDNameBytes + "A mechanism intended for user-level extension that defines the + decoding rules for the local-part representation of arbitrary + Clojure / Java Objects when used for computing namespaced + identifiers." + + (as-byte-array [x] + "Extract a byte serialization that represents the 'name' of x, + typically unique within a given namespace.")) + +(defprotocol UUIDable + "A UUIDable object directly represents a UUID. Examples of things which + might be conceptually 'uuidable' include string representation of a + UUID in canonical hex format, or an appropriate URN URI." + + (as-uuid ^java.util.UUID [x] + "Coerce the value 'x' to a UUID.") + + (uuidable? [x] + "Return 'true' if 'x' can be coerced to UUID.")) + +(defprotocol UUIDRfc9562 + "A protocol that abstracts an unique identifier as described by + IETF RFC9562 . A UUID + represents a 128-bit value, however there are variant encoding + layouts used to assign and interpret information encoded in + those bits. This is a protocol for _variant 2_ (*Leach-Salz*) + UUID's." + + (hash-code [uuid] + "Return a suitable 64-bit hash value for `uuid`. Extend with + specialized hash computation.") + + (null? [uuid] + "Return `true` only if `uuid` has all 128 bits set to zero and is + therefore equal to the null UUID, 00000000-0000-0000-0000-000000000000.") + + (max? [uuid] + "Return `true` only if `uuid` has all 128 bits set and is + therefore equal to the maximum UUID, ffffffff-ffff-ffff-ffff-ffffffffffff.") + + (uuid? [x] + "Return `true` if `x` implements an RFC9562 unique identifier.") + + (uuid= [x y] + "Directly compare two UUID's for = relation based on the equality + semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. + See: `clj-uuid/=`") + + (uuid< [x y] + "Directly compare two UUID's for < relation based on the ordinality + semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. + See: `clj-uuid/<`") + + (uuid> [x y] + "Directly compare two UUID's for > relation based on the ordinality + semantics defined by [RFC4122:3 RULES FOR LEXICAL EQUIVALENCE]. + See: `clj-uuid/>`") + + (get-word-high [uuid] + "Return the most significant 64 bits of UUID's 128 bit value.") + + (get-word-low [uuid] + "Return the least significant 64 bits of UUID's 128 bit value.") + + (get-version [uuid] + "Return the version number associated with this UUID. The version + field contains a value which describes the nature of the UUID. There + are five versions of Leach-Salz UUID, plus the null and max UUIDs: + + 0x0 Null + 0x1 Time based + 0x2 DCE security with POSIX UID + 0x3 Namespaced, deterministic (MD5 Digest) + 0x4 Cryptographic random + 0x5 Namespaced, deterministic (SHA1 Digest) + 0x6 Time based, lexically ordered + 0x7 POSIX Time based, lexically ordered, cryptographically secure + 0x8 User Customizable + 0xF Max + + In the canonical representation, xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx, + the four bits of M indicate the UUID version (i.e., the hexadecimal M + will be either 1, 2, 3, 4, 5, 6, 7, or 8).") + + (get-variant [uuid] + "Return the variant number associated with this UUID. The variant field + contains a value which identifies the layout of the UUID. The bit-layout + implemented by this protocol supports UUID's with a variant value of 0x2, + which indicates Leach-Salz layout. Defined UUID variant values are: + + 0x0 Null + 0x2 Leach-Salz + 0x6 Microsoft + 0x7 Max + + In the canonical representation, xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx, + the most significant bits of N indicate the variant (depending on the + variant one, two, or three bits are used). The variant covered by RFC9562 + is indicated by the two most significant bits of N being 1 0 (i.e., the + hexadecimal N will always be 8, 9, A, or B).") + + (get-time-low [uuid] + "Return the 32 bit unsigned value that represents the `time-low` field + of the `timestamp` associated with this UUID.") + + (get-time-mid [uuid] + "Return the 16 bit unsigned value that represents the `time-mid` field + of the `timestamp` assocaited with this UUID.") + + (get-time-high [uuid] + "Return the 16 bit unsigned value that represents the `time-high` field + of the `timestamp` multiplexed with the `version` of this UUID.") + + (get-clk-high [uuid] + "Return the 8 bit unsigned value that represents the most significant + byte of the `clk-seq` multiplexed with the `variant` of this UUID.") + + (get-clk-low [uuid] + "Return the 8 bit unsigned value that represents the least significant + byte of the `clk-seq` associated with this UUID.") + + (get-clk-seq [uuid] + "Return the clock-sequence number associated with this UUID. For time-based + (v1, v6) UUID's the 'clock-sequence' value is a somewhat counter-intuitively + named seed-value that is used to reduce the potential that duplicate UUID's + might be generated under unusual situations, such as if the system hardware + clock is set backward in time or if, despite all efforts otherwise, a + duplicate node-id happens to be generated. This value is initialized to + a random 16-bit number once per lifetime of the system. For + non-gregorian-time-based (v3, v4, v5, v7, v8, squuid) UUID's, always + returns `nil`.") + + (get-node-id [uuid] + "Return the 48 bit unsigned value that represents the spatially unique + node identifier associated with this UUID.") + + (get-timestamp [uuid] + "Return the time of UUID creation. For Gregorian time-based (v1, + v6) UUID's, this is 60 bit unsigned value that represents a + temporally unique timestamp associated with this UUID. The result + encodes the number of 100 nanosecond intervals since the adoption of + the Gregorian calendar. For v7 UUID's this encodes the more common + unix time in milliseconds since midnight, January 1, 1970 UTC. For + non-time-based (v3, v4, v5, v8, squuid) UUID's, always returns + `nil`.") + + (get-instant ^java.util.Date [uuid] + "For time-based (v1, v6, v7) UUID's, return a java.util.Date + object that represents the system time at which this UUID was + generated. NOTE: the returned value may not necessarily be + temporally unique. For non-time-based + (v3, v4, v5, v8, squuid) UUID's, always returns `nil`.") + + (get-unix-time [uuid] + "For time-based (v1, v6, v7) UUIDs return the timestamp portion in + aproximately milliseconds since the Unix epoch 1970-01-01T00:00:00.000Z. + For non-time-based (v3, v4, v5, v8, squuid) UUID's, always returns `nil`.") + + (to-byte-array [uuid] + "Return an array of 16 bytes that represents `uuid` as a decomposed + octet serialization encoded in most-significant-byte first order.") + + (to-string ^String [uuid] + "Return a String object that represents `uuid` in the canonical + 36 character hex-string format: + + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + + (to-hex-string ^String [uuid] + "Return a String object that represents `uuid` as the 32 hexadecimal + characters directly encodong the UUID's 128 bit value: + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + + (to-urn-string ^String [uuid] + "Return a String object that represents `uuid` as a the string + serialization of the URN URI based on the canonical 36 character + hex-string representation: + + urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + + (to-uri ^java.net.URI [uuid] + "Return the unique URN URI associated with this UUID.")) + +;; For backwards compatibility + +(def UUIDRfc4122 UUIDRfc9562) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; RFC9562 Unique Identifier extended java.util.UUID +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(extend-type UUID + + UUIDable + + (as-uuid [u] u) + (uuidable? [_] true) + + UUIDRfc9562 + + (uuid? ^boolean [_] true) + + (uuid= ^boolean [^UUID x ^UUID y] + (.equals x y)) + + (uuid< ^boolean [^UUID x ^UUID y] + (let [xh (.getMostSignificantBits x) + yh (.getMostSignificantBits y)] + (or (clojure/< xh yh) + (and (clojure/= xh yh) (clojure/< (.getLeastSignificantBits x) + (.getLeastSignificantBits y)))))) + + (uuid> ^boolean [^UUID x ^UUID y] + (let [xh (.getMostSignificantBits x) + yh (.getMostSignificantBits y)] + (or (clojure/> xh yh) + (and (clojure/= xh yh) (clojure/> (.getLeastSignificantBits x) + (.getLeastSignificantBits y)))))) + + (get-word-high ^long [uuid] + (.getMostSignificantBits uuid)) + + (get-word-low ^long [uuid] + (.getLeastSignificantBits uuid)) + + (null? ^boolean [uuid] + (clojure/= 0 (.getMostSignificantBits uuid) (.getLeastSignificantBits uuid))) + + (max? ^boolean [uuid] + (uuid= uuid +max+)) + + (to-byte-array ^bytes [uuid] + (let [arr (byte-array 16)] + (bitmop/long->bytes (.getMostSignificantBits uuid) arr 0) + (bitmop/long->bytes (.getLeastSignificantBits uuid) arr 8) + arr)) + + (hash-code ^long [uuid] + (long (.hashCode uuid))) + + (get-version ^int [uuid] + (.version uuid)) + + (get-variant ^int [uuid] + (.variant uuid)) + + (to-string [uuid] + (.toString uuid)) + + (to-urn-string [uuid] + (str "urn:uuid:" (.toString uuid))) + + (to-hex-string [uuid] + (str (bitmop/hex (get-word-high uuid)) (bitmop/hex (get-word-low uuid)))) + + (to-uri [uuid] + (URI/create (to-urn-string uuid))) + + (get-time-low ^long [uuid] + (let [msb (.getMostSignificantBits uuid)] + (if (clojure/= 6 (get-version uuid)) + (bitmop/ldb #=(bitmop/mask 16 0) msb) + (bitmop/ldb #=(bitmop/mask 32 0) (bit-shift-right msb 32))))) + + (get-time-mid ^long [uuid] + (bitmop/ldb #=(bitmop/mask 16 16) + (.getMostSignificantBits uuid))) + + (get-time-high ^long [uuid] + (let [msb (.getMostSignificantBits uuid)] + (if (clojure/= 6 (get-version uuid)) + (bitmop/ldb #=(bitmop/mask 32 0) (bit-shift-right msb 32)) + (bitmop/ldb #=(bitmop/mask 16 0) msb)))) + + (get-clk-low ^long [uuid] + (bitmop/ldb #=(bitmop/mask 8 0) + (bit-shift-right (.getLeastSignificantBits uuid) 56))) + + (get-clk-high ^long [uuid] + (bitmop/ldb #=(bitmop/mask 8 48) + (.getLeastSignificantBits uuid))) + + (get-clk-seq ^short [uuid] + (when (#{1 6} (.version uuid)) + (.clockSequence uuid))) + + (get-node-id ^long [uuid] + (bitmop/ldb #=(bitmop/mask 48 0) + (.getLeastSignificantBits uuid))) + + (get-timestamp ^long [uuid] + (case (.version uuid) + 1 (.timestamp uuid) + 6 (bit-or (bitmop/ldb #=(bitmop/mask 12 0) + (.getMostSignificantBits uuid)) + (bit-shift-left (get-time-mid uuid) 12) + (bit-shift-left (get-time-high uuid) 28)) + 7 (bitmop/ldb #=(bitmop/mask 48 16) (.getMostSignificantBits uuid)) + nil)) + + (get-unix-time ^long [uuid] + (case (.version uuid) + (1 6) (clock/posix-time (get-timestamp uuid)) + 7 (get-timestamp uuid) + nil)) + + (get-instant [uuid] + (when-let [ts (get-unix-time uuid)] + (Date. (long ts)))) + + UUIDNameBytes + + (as-byte-array + ^bytes + [this] + (to-byte-array this))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; V0 UUID Constructor [RFC9562:5.9] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn null + "Generates the v0 (null) UUID, 00000000-0000-0000-0000-000000000000." + ^java.util.UUID + [] + +null+) + +(defn v0 + "Generates the v0 (null) UUID, 00000000-0000-0000-0000-000000000000." + ^java.util.UUID + [] + +null+) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The MAX UUID Constructor [RFC9562:5.10] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn max + "Generates the v15 (maximum) UUID, ffffffff-ffff-ffff-ffff-ffffffffffff." + ^java.util.UUID + [] + +max+) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; V1, V6 (time-coded) UUID Constructors [RFC9562:5.1, 5.6] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Concatenate the UUID version with a unique per-node identifier (the +;; MAC address of the computer that is generating the UUID, or securely +;; generated once-per-session random identifier) and with a monotonic +;; timestamp based on the number of 100-nanosecond intervals since the +;; adoption of the Gregorian calendar in the West, 12:00am Friday +;; October 15, 1582 UTC. + +(defn v1 + "Generate a v1 (time-based) unique identifier, guaranteed to be unique + and thread-safe regardless of clock precision or degree of concurrency. + Creation of v1 UUID's does not require any call to a cryptographic + generator and can be accomplished much more efficiently than v3, v4, v5, v7, + or squuid's. A v1 UUID reveals both the identity of the computer that + generated the UUID and the time at which it did so. Its uniqueness across + computers is guaranteed as long as MAC addresses are not duplicated." + ^java.util.UUID + [] + (let [ts (clock/monotonic-time) + time-low (bitmop/ldb #=(bitmop/mask 32 0) ts) + time-mid (bitmop/ldb #=(bitmop/mask 16 32) ts) + time-high (bitmop/dpb #=(bitmop/mask 4 12) + (bitmop/ldb #=(bitmop/mask 12 48) ts) 0x1) + msb (bit-or time-high + (bit-shift-left time-low 32) + (bit-shift-left time-mid 16))] + (UUID. msb (node/+v1-lsb+)))) + +(defn v6 + "Generate a v6 (time-based), LEXICALLY SORTABLE, unique identifier, + v6 is a field-compatible version of v1, reordered for improved DB + locality. Creation of v6 UUID's does not require any call to a + cryptographic generator and can be accomplished much more efficiently + than v3, v4, v5, v7, or squuid's. A v6 UUID uses a cryptographically + secure, hard to guess random node id. It DOES NOT reveal the identity + of the computer on which it was created." + ^java.util.UUID + [] + (let [ts (clock/monotonic-time) + time-high (bitmop/ldb #=(bitmop/mask 32 28) ts) + time-mid (bitmop/ldb #=(bitmop/mask 16 12) ts) + time-low (bitmop/dpb #=(bitmop/mask 4 12) + (bitmop/ldb #=(bitmop/mask 12 0) ts) 0x6) + msb (bit-or time-low + (bit-shift-left time-mid 16) + (bit-shift-left time-high 32))] + (UUID. msb (node/+v6-lsb+)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; V7 (crypto secure, posix time-coded) UUID Constructor [RFC9562:5.7] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; 0 1 2 3 +;; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | unix_ts_ms | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | unix_ts_ms | ver | rand_a | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; |var| rand_b | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +;; | rand_b | +;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +;; The following table enumerates a slot/type/value correspondence: +;; +;; SLOT SIZE Description +;; ---------------------------------------------------------------------- +;; unix_ts_ms 6 bytes POSIX millis timestamp +;; ver 4 bits version, set to 0b0111 (7) +;; rand_a 12 bits randomly initialized subcounter +;; var 2 bits variant, set to 0b10 (2) +;; rand_b 62 bits cryptographically secure pseudorandom data + +(defn v7 + "Generate a v7 unix time-based, LEXICALLY SORTABLE UUID with monotonic + counter and cryptographically secure random portion and POSIX time encoding. + As such, creation of v7 UUIDs may be significantly slower, but have improved + entropy chararacteristics compared to v1 or v6 UUIDs." + ^java.util.UUID + [] + (let [[t counter] (clock/monotonic-unix-time-and-random-counter) + time (bitmop/ldb #=(bitmop/mask 48 0) t) + ver-and-counter (bitmop/dpb #=(bitmop/mask 4 12) counter 0x7) + msb (bit-or ver-and-counter (bit-shift-left time 16)) + lsb (bitmop/dpb #=(bitmop/mask 2 62) (random/long) 0x2)] + (UUID. msb lsb))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; V4 (random) UUID Constructor [RFC9562:5.4] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn v4 + "Generate a v4 (random) UUID. Uses default JVM implementation. If two + arguments, lsb and msb (both long) are provided, then construct a valid, + properly formatted v4 UUID based on those values. So, for example the + following UUID, created from all zero bits, is indeed distinct from the + null UUID: + + (v4) + => #uuid \"dcf0035f-ea29-4d1c-b52e-4ea499c6323e\" + + (v4 0 0) + => #uuid \"00000000-0000-4000-8000-000000000000\" + + (null) + => #uuid \"00000000-0000-0000-0000-000000000000\"" + (^java.util.UUID [] + (UUID/randomUUID)) + (^java.util.UUID [msb lsb] + (UUID. + (bitmop/dpb #=(bitmop/mask 4 12) msb 0x4) + (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; v8 (custom) UUID Constructor [RFC9562:5.8: UUID Version 8];; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn v8 + "Generate a v8 custom UUID with up to 122 bits of user data." + ^java.util.UUID + [^long msb ^long lsb] + (UUID. + (bitmop/dpb #=(bitmop/mask 4 12) msb 0x8) + (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; SQUUID (sequential) UUID Constructor +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn squuid + "Generate a SQUUID (sequential, random) unique identifier. SQUUID's + are a nonstandard variation on v4 (random) UUIDs that have the + desirable property that they increase sequentially over time as well + as encode retrievably the posix time at which they were generated. + Splits and reassembles a v4 UUID to merge current POSIX + time (seconds since 12:00am January 1, 1970 UTC) with the most + significant 32 bits of the UUID." + ^java.util.UUID + [] + (let [uuid (v4) + secs (clock/posix-time) + lsb (get-word-low uuid) + msb (get-word-high uuid) + timed-msb (bit-or (bit-shift-left secs 32) + (bit-and +ub32-mask+ msb))] + (UUID. timed-msb lsb))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; "Local-Part" Representation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The following represent a default set of local-part encoding rules. As a +;; default, a plain byte-array will be passed through unchanged and a +;; generic java.lang.Object is represented by the bytes of its serialization. +;; Strings are represented using UTF8 encoding. URL's are digested as the +;; UTF bytes of their string representation. + +(def ^:private ByteArray (class (byte-array 0))) + +(extend-protocol UUIDNameBytes + + java.lang.Object + (as-byte-array ^bytes [this] + (if (instance? ByteArray this) + this + (let [baos (ByteArrayOutputStream.) + oos (ObjectOutputStream. baos)] + (.writeObject oos this) + (.close oos) + (.toByteArray baos)))) + + java.lang.String + (as-byte-array ^bytes [this] + (util/compile-if (java6?) + (.getBytes this) + (.getBytes this java.nio.charset.StandardCharsets/UTF_8))) + + java.net.URL + (as-byte-array ^bytes [this] + (as-byte-array (.toString this))) + + nil + (as-byte-array [x] + (throw (IllegalArgumentException. (format "%s cannot be converted to byte array." x))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Digest Instance +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- make-digest + ^java.security.MessageDigest + [^String designator] + (MessageDigest/getInstance designator)) + +(defn- digest-bytes + ^bytes + [^String kind ^bytes ns-bytes ^bytes local-bytes] + (let [m (make-digest kind)] + (.update m ns-bytes) + (.digest m local-bytes))) + +(defn- build-digested-uuid + ^java.util.UUID + [^long version ^bytes arr] + {:pre [(or (clojure/= version 3) (clojure/= version 5))]} + (let [msb (bitmop/bytes->long arr 0) + lsb (bitmop/bytes->long arr 8)] + (UUID. + (bitmop/dpb #=(bitmop/mask 4 12) msb version) + (bitmop/dpb #=(bitmop/mask 2 62) lsb 0x2)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Namespaced UUIDs [RFC9562:5.3, 5.5] ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn v3 + "Generate a v3 (name based, MD5 hash) UUID. 'context' must be UUIDable. + v3 identifiers are intended for generating UUID's from names that are + drawn from, and unique within, some namespace. The concept of name and + namespace should be broadly construed, and not limited to textual names. + The requiremens for a v3 UUID are as follows: + + * v3 UUID's generated at different times from the same name in the same + namespace MUST be equal. + + * v3 UUID's generated from two different names in the same namespace + SHOULD be distinct with a high degree of certainty. + + * v3 UUID's generated from the same name in two different namespaces + SHOULD be distinct with a high degree of certainty. + + * If two v3 UUID's are equal, then there is a high degree of certainty + that they were generated from the same name in the same namespace." + ^java.util.UUID + [context local-part] + (build-digested-uuid 3 + (digest-bytes +md5+ + (to-byte-array (as-uuid context)) + (as-byte-array local-part)))) + + +(defn v5 + "Generate a v5 (name based, SHA1 hash) UUID. 'context' must be UUIDable. + v5 identifiers are intended for generating UUID's from names that are + drawn from, and unique within, some namespace. The concept of name and + namespace should be broadly construed, and not limited to textual names. + The requiremens for a v5 UUID are as follows: + + * v5 UUID's generated at different times from the same name in the same + namespace MUST be equal. + + * v5 UUID's generated from two different names in the same namespace + SHOULD be distinct with a high degree of certainty. + + * v5 UUID's generated from the same name in two different namespaces + SHOULD be distinct with a high degree of certainty. + + * If two v5 UUID's are equal, then there is a high degree of certainty + that they were generated from the same name in the same namespace." + ^java.util.UUID + [context local-part] + (build-digested-uuid 5 + (digest-bytes +sha1+ + (to-byte-array (as-uuid context)) + (as-byte-array local-part)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- compare-many + [f x y more] + (if (f x y) + (if (next more) + (recur f y (first more) (next more)) + (f y (first more))) + false)) + +(defn = + "Directly compare two or more UUIDs for = relation based on the + equality semantics defined by [RFC4122:3 RULES FOR LEXICAL + EQUIVALENCE]." + ([_] true) + ([x y] + (uuid= x y)) + ([x y & more] + (compare-many uuid= x y more))) + +(defn > + "Directly compare two or more UUIDs for > relation based on the + ordinality semantics defined by [RFC4122:3 RULES FOR LEXICAL + EQUIVALENCE]." + ([_] true) + ([x y] + (uuid> x y)) + ([x y & more] + (compare-many uuid> x y more))) + +(defn < + "Directly compare two or more UUIDs for < relation based on the + ordinality semantics defined by [RFC4122:3 RULES FOR LEXICAL + EQUIVALENCE]." + ([_] true) + ([x y] + (uuid< x y)) + ([x y & more] + (compare-many uuid< x y more))) + +(defn uuid-string? [str] + (and (string? str) + (some? (re-matches uuid-regex str)))) + +(defn uuid-urn-string? [str] + (and (string? str) + (some? (re-matches urn-regex str)))) + +(defn uuid-vec? [v] + (and (clojure/= (count v) 16) + (every? #(and (integer? %) (>= -128 % 127)) v))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; UUID Polymorphism +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- str->uuid [s] + (cond + (uuid-string? s) (UUID/fromString s) + (uuid-urn-string? s) (UUID/fromString (subs s 9)) + :else (throw (IllegalArgumentException. (format "Invalid UUID: %s" s))))) + +(extend-protocol UUIDRfc9562 + Object + (uuid? [x] false) + + nil + (uuid? [_] false)) + +(extend-protocol UUIDable + (Class/forName "[B") ; byte array + (as-uuid [^bytes ba] + (let [bb (ByteBuffer/wrap ba)] + (UUID. (.getLong bb) (.getLong bb)))) + (uuidable? [^bytes ba] + (clojure/= 16 (alength ^bytes ba))) + + String + (uuidable? ^boolean [s] + (or + (uuid-string? s) + (uuid-urn-string? s))) + (as-uuid [s] + (str->uuid s)) + + URI + (uuidable? ^boolean [u] + (uuid-urn-string? (str u))) + (as-uuid [u] + (str->uuid (str u))) + + Object + (uuidable? ^boolean [_] + false) + (as-uuid [x] + (throw (IllegalArgumentException. (format "%s Cannot be coerced to UUID." x)))) + + nil + (as-uuid [x] + (throw (IllegalArgumentException. (format "%s cannot be coerced to UUID." x)))) + (uuidable? ^boolean [_] false)) diff --git a/src/clj_uuid/random.clj b/src/clj_uuid/random.clj index 79f6b25..4c7c5a7 100644 --- a/src/clj_uuid/random.clj +++ b/src/clj_uuid/random.clj @@ -28,13 +28,31 @@ (.nextBytes ^SecureRandom @secure-random bs) bs)) (defn long - "Generate a long value that is hard to guess. limited to the number of bytes." + "Generate a long value that is hard to guess. Randomness limited to the + number of bytes." ([] (long 8)) ([n-bytes] (reduce (fn [n b] (+ (bit-shift-left n 8) b)) 0 (bytes n-bytes)))) + + (defn eight-bits "Generate a hard-to-guess long value between 0 and 255" [] (bit-and (long 1) 0xff)) + +(defn ten-bits + "Generate a hard-to-guess long value between 0 and 1023" + [] + (bit-and (long 2) 0x3ff)) + +(defn eleven-bits + "Generate a hard-to-guess long value between 0 and 2047" + [] + (bit-and (long 2) 0x7ff)) + +(defn twelve-bits + "Generate a hard-to-guess long value between 0 and 4095" + [] + (bit-and (long 2) 0xfff)) diff --git a/test/clj_uuid/api_test.clj b/test/clj_uuid/api_test.clj index df7b382..a59fc14 100644 --- a/test/clj_uuid/api_test.clj +++ b/test/clj_uuid/api_test.clj @@ -1,7 +1,7 @@ (ns clj-uuid.api-test (:refer-clojure :exclude [uuid? max]) - (:require [clojure.test :refer :all] - [clj-uuid :refer :all :exclude [= > <]]) + (:require [clj-uuid.core :refer :all :exclude [= > <]] + [clojure.test :refer :all]) (:import (java.lang IllegalArgumentException))) diff --git a/test/clj_uuid/bitmop_test.clj b/test/clj_uuid/bitmop_test.clj index 875ecf2..e559061 100644 --- a/test/clj_uuid/bitmop_test.clj +++ b/test/clj_uuid/bitmop_test.clj @@ -1,10 +1,8 @@ (ns clj-uuid.bitmop-test (:require - [clojure.test :refer :all] - [clj-uuid.bitmop :refer :all])) + [clj-uuid.bitmop :refer :all] + [clojure.test :refer :all])) - - (deftest check-bit-mask-operators (testing "bit-mask construction..." (is (= (mask 0 0) 0)) @@ -46,7 +44,7 @@ (is (= 32 (mask-width (mask 32 0)))) (is (= 31 (mask-width (mask 31 32)))) (is (= 62 (mask-width (mask 62 0)))) - (is (= 48 (mask-width (mask 48 15)))) + (is (= 48 (mask-width (mask 48 15)))) (is (= 64 (mask-width (mask 64 0)))) (is (= 60 (mask-width (mask 60 4)))) (is (= 31 (mask-width (mask 31 33)))) @@ -77,7 +75,7 @@ (for [i (range 0 61)] (is (= 15 (ldb (mask 4 i) (mask 64 0)))))) (testing "dpb..." - (map #(is (= 0x3 %)) + (map #(is (= 0x3 %)) (for [i (range 8)] (ldb (mask 4 (* i 4)) (dpb (mask 4 (* i 4)) (mask 64 0) 0x3)))) @@ -124,17 +122,17 @@ (is (= (ub4 -1) 15)) (is (= (ub4 16) 0)) (is (= (ub4 15) 15)) - (is (= (ub4 7) 7)) + (is (= (ub4 7) 7)) (is (= (ub56 0x80) 128)) (is (= (class (ub56 0x80)) Long)))) (deftest check-byte-reassembly-roundtrip - (testing "dissasemble/reassemble-bytes..." + (testing "dissasemble/reassemble-bytes..." (dotimes [_ 256] (let [bytes (for [i (range 8)] - (sb8 (rand-int (mask 8 0))))] + (sb8 (rand-int (mask 8 0))))] (is (= (seq (long->bytes (assemble-bytes bytes))) bytes)))))) @@ -142,7 +140,7 @@ (testing "octet-hex mapping..." (is (= (octet-hex 0xff) "FF")) (is (= (octet-hex 0x00) "00")) - (is (= (octet-hex 0x7a) "7A")) + (is (= (octet-hex 0x7a) "7A")) (is (= (octet-hex 15) "0F")) (is (= (octet-hex 45) "2D")) (is (= (octet-hex 250) "FA")) @@ -160,6 +158,3 @@ (is (= (hex 255) "00000000000000FF")) (is (= (hex 65536) "0000000000010000")) (is (= (hex -1) "FFFFFFFFFFFFFFFF")))) - - - diff --git a/test/clj_uuid/clock_test.clj b/test/clj_uuid/clock_test.clj index 77a3b3d..57104ad 100644 --- a/test/clj_uuid/clock_test.clj +++ b/test/clj_uuid/clock_test.clj @@ -1,7 +1,7 @@ (ns clj-uuid.clock-test - (:require [clojure.test :refer :all] - [clojure.set] - [clj-uuid.clock :refer :all])) + (:require [clj-uuid.clock :as clock] + [clojure.set :as set] + [clojure.test :refer :all])) (deftest check-single-threaded (let [iterations 1000000 @@ -9,11 +9,11 @@ check #(mapv (fn [_] (%)) (range iterations))] (testing "monotonic-time..." (dotimes [_ groups] - (let [result (check monotonic-time)] + (let [result (check clock/monotonic-time)] (is (= (count result) (count (set result))))))) (testing "monotonic-unix-time-and-random-counter..." (dotimes [_ groups] - (let [result (check monotonic-unix-time-and-random-counter)] + (let [result (check clock/monotonic-unix-time-and-random-counter)] (is (= (count result) (count (set result))))))))) (deftest check-multi-threaded-monotonic-time @@ -22,13 +22,13 @@ agents (mapv agent (repeat concur nil)) working (mapv #(send-off % (fn [state] - (repeatedly extent monotonic-time))) + (repeatedly extent clock/monotonic-time))) agents) _ (apply await working) answers (mapv deref working)] (testing (str "concurrent timestamp uniqueness (" concur " threads)...") (is (= (* concur extent) - (count (apply clojure.set/union (map set answers)))))) + (count (apply set/union (map set answers)))))) (testing (str "concurrent monotonic increasing (" concur " threads)...") (is (every? identity (map #(apply < %) answers))))))) @@ -40,14 +40,14 @@ working (mapv #(send-off % (fn [state] (repeatedly extent - monotonic-unix-time-and-random-counter))) + clock/monotonic-unix-time-and-random-counter))) agents) _ (apply await working) answers (mapv deref working)] (testing (str "concurrent timestamp uniqueness (" concur " threads)...") (is (= (* concur extent) - (count (apply clojure.set/union (map set answers)))))) + (count (apply set/union (map set answers)))))) (testing (str "concurrent monotonic increasing (" concur " threads)...") (doseq [answer answers] (let [[time counter] (first answer)] diff --git a/test/clj_uuid/node_test.clj b/test/clj_uuid/node_test.clj index eb3fa64..ffa36d5 100644 --- a/test/clj_uuid/node_test.clj +++ b/test/clj_uuid/node_test.clj @@ -1,13 +1,13 @@ (ns clj-uuid.node-test - (:require [clojure.test :refer :all] - [clj-uuid.node :refer :all])) + (:require [clj-uuid.node :as node] + [clojure.test :refer :all])) (deftest check-node-id (testing "existance and type of node id...") - (is (= (node-id) (node-id))) - (is (coll? (node-id))) - (is (= 6 (count (node-id)))) - (is (every? number? (node-id))) - (is (= 1 (bit-and 0x01 @+node-id+))) - (is (instance? Long @+node-id+))) + (is (= (node/node-id) (node/node-id))) + (is (coll? (node/node-id))) + (is (= 6 (count (node/node-id)))) + (is (every? number? (node/node-id))) + (is (= 1 (bit-and 0x01 @node/+node-id+))) + (is (instance? Long @node/+node-id+))) diff --git a/test/clj_uuid/v1_test.clj b/test/clj_uuid/v1_test.clj index af36685..391a093 100644 --- a/test/clj_uuid/v1_test.clj +++ b/test/clj_uuid/v1_test.clj @@ -1,16 +1,16 @@ (ns clj-uuid.v1-test "Time based UUIDs tests" - (:require [clojure.test :refer :all] - [clojure.set] - [clj-uuid :as uuid :refer [v1 get-timestamp]] - [clj-uuid.clock :as clock])) + (:require [clj-uuid.clock :as clock] + [clj-uuid.core :as uuid] + [clojure.set :as set] + [clojure.test :refer :all])) (deftest check-v1-single-threaded (let [iterations 1000000 groups 10] (testing "single-thread v1 uuid uniqueness..." (dotimes [_ groups] - (let [result (repeatedly iterations v1)] + (let [result (repeatedly iterations uuid/v1)] (is (= (count result) (count (set result))))))))) (deftest check-v1-concurrency @@ -19,19 +19,19 @@ agents (map agent (repeat concur nil)) working (map #(send-off % (fn [state] - (repeatedly extent v1))) + (repeatedly extent uuid/v1))) agents) _ (apply await working) answers (map deref working)] (testing (str "concurrent v1 uuid uniqueness (" concur " threads)...") (is (= (* concur extent) - (count (apply clojure.set/union (map set answers)))))) + (count (apply set/union (map set answers)))))) (testing (str "concurrent v1 monotonic increasing (" concur " threads)...") (is (every? identity - (map #(apply < (map get-timestamp %)) answers))))))) + (map #(apply < (map uuid/get-timestamp %)) answers))))))) (deftest check-get-timestamp (let [time (clock/monotonic-time)] (with-redefs [clock/monotonic-time (constantly time)] - (is (= time (uuid/get-timestamp (v1))) + (is (= time (uuid/get-timestamp (uuid/v1))) "Timestamp should be retrievable from v1 UUID")))) diff --git a/test/clj_uuid/v3_test.clj b/test/clj_uuid/v3_test.clj index 0601ce2..67345ac 100644 --- a/test/clj_uuid/v3_test.clj +++ b/test/clj_uuid/v3_test.clj @@ -1,25 +1,24 @@ (ns clj-uuid.v3-test - (:refer-clojure :exclude [uuid? max]) - (:require [clojure.test :refer :all] - [clj-uuid :refer :all :exclude [> < =]])) + (:require [clj-uuid.core :as uuid] + [clojure.test :refer :all])) (deftest check-v3-special-cases (testing "v3 special case correctness..." (is (= - (v3 +null+ "") + (uuid/v3 uuid/+null+ "") #uuid "4ae71336-e44b-39bf-b9d2-752e234818a5")) (is (= - (v3 +namespace-x500+ "") + (uuid/v3 uuid/+namespace-x500+ "") #uuid "7AAF118C-F174-3EBA-9EC5-680CD791A020")) (is (= - (v3 +namespace-oid+ "") + (uuid/v3 uuid/+namespace-oid+ "") #uuid "596B79DC-00DD-3991-A72F-D3696C38C64F")) (is (= - (v3 +namespace-dns+ "") + (uuid/v3 uuid/+namespace-dns+ "") #uuid "C87EE674-4DDC-3EFE-A74E-DFE25DA5D7B3")) (is (= - (v3 +namespace-url+ "") + (uuid/v3 uuid/+namespace-url+ "") #uuid "14CDB9B4-DE01-3FAA-AFF5-65BC2F771745")))) @@ -125,7 +124,7 @@ (deftest check-v3-null-ns-cases (testing "v3 null-ns case-based correctness..." (doseq [case +v3-null-ns-cases+] - (is (= (second case) (v3 +null+ (first case))))))) + (is (= (second case) (uuid/v3 uuid/+null+ (first case))))))) (def +v3-dns-ns-cases+ @@ -230,7 +229,7 @@ (deftest check-v3-dns-ns-cases (testing "v3 dns-ns case-based correctness..." (doseq [case +v3-dns-ns-cases+] - (is (= (second case) (v3 +namespace-dns+ (first case))))))) + (is (= (second case) (uuid/v3 uuid/+namespace-dns+ (first case))))))) (def +v3-oid-ns-cases+ @@ -335,4 +334,4 @@ (deftest check-v3-oid-ns-cases (testing "v3 oid-ns case-based correctness..." (doseq [case +v3-oid-ns-cases+] - (is (= (second case) (v3 +namespace-oid+ (first case))))))) + (is (= (second case) (uuid/v3 uuid/+namespace-oid+ (first case))))))) diff --git a/test/clj_uuid/v4_test.clj b/test/clj_uuid/v4_test.clj index e527310..ade0a13 100644 --- a/test/clj_uuid/v4_test.clj +++ b/test/clj_uuid/v4_test.clj @@ -1,13 +1,12 @@ (ns clj-uuid.v4-test "Custom UUIDs tests" - (:refer-clojure :exclude [uuid? max]) - (:require [clojure.test :refer :all] - [clj-uuid :refer :all :exclude [> < =]])) + (:require [clj-uuid.core :as uuid] + [clojure.test :refer :all])) (deftest check-v4-special-cases (testing "v4 special case correctness..." - (is (= (v4 0 0) #uuid "00000000-0000-4000-8000-000000000000")) - (is (= (v4 0 1) #uuid "00000000-0000-4000-8000-000000000001")) - (is (= (v4 0 -1) #uuid "00000000-0000-4000-bfff-ffffffffffff")) - (is (= (v4 -1 0) #uuid "ffffffff-ffff-4fff-8000-000000000000")) - (is (= (v4 -1 -1) #uuid "ffffffff-ffff-4fff-bfff-ffffffffffff")))) + (is (= (uuid/v4 0 0) #uuid "00000000-0000-4000-8000-000000000000")) + (is (= (uuid/v4 0 1) #uuid "00000000-0000-4000-8000-000000000001")) + (is (= (uuid/v4 0 -1) #uuid "00000000-0000-4000-bfff-ffffffffffff")) + (is (= (uuid/v4 -1 0) #uuid "ffffffff-ffff-4fff-8000-000000000000")) + (is (= (uuid/v4 -1 -1) #uuid "ffffffff-ffff-4fff-bfff-ffffffffffff")))) diff --git a/test/clj_uuid/v5_test.clj b/test/clj_uuid/v5_test.clj index 153fb82..ae0180a 100644 --- a/test/clj_uuid/v5_test.clj +++ b/test/clj_uuid/v5_test.clj @@ -1,26 +1,23 @@ (ns clj-uuid.v5-test - (:refer-clojure :exclude [uuid? max]) - (:require [clojure.test :refer :all] - [clj-uuid :refer :all :exclude [> < =]])) - - + (:require [clj-uuid.core :as uuid] + [clojure.test :refer :all])) (deftest check-v5-special-cases (testing "v5 special case correctness..." (is (= - (v5 +null+ "") + (uuid/v5 uuid/+null+ "") #uuid "E129F27C-5103-5C5C-844B-CDF0A15E160D")) (is (= - (v5 +namespace-x500+ "") + (uuid/v5 uuid/+namespace-x500+ "") #uuid "B4BDF874-8C03-5BD8-8FD7-5E409DFD82C0")) (is (= - (v5 +namespace-oid+ "") + (uuid/v5 uuid/+namespace-oid+ "") #uuid "0A68EB57-C88A-5F34-9E9D-27F85E68AF4F")) (is (= - (v5 +namespace-dns+ "") + (uuid/v5 uuid/+namespace-dns+ "") #uuid "4EBD0208-8328-5D69-8C44-EC50939C0967")) (is (= - (v5 +namespace-url+ "") + (uuid/v5 uuid/+namespace-url+ "") #uuid "1B4DB7EB-4057-5DDF-91E0-36DEC72071F5")))) (def +v5-null-ns-cases+ @@ -118,15 +115,13 @@ ("{|}~ !\"#$%&'()*+,-./012345" #uuid "37CDD9D8-A94F-5BB5-AA57-97A92ACA22FC") ("|}~ !\"#$%&'()*+,-./0123456" #uuid "BB23D8C2-29F0-5EC5-BF50-B7092FA62204") ("}~ !\"#$%&'()*+,-./01234567" #uuid "AD3AD027-A2ED-5F09-B581-78AD87D86A7C") - ("~ !\"#$%&'()*+,-./012345678" #uuid "093B7461-98EF-55DC-8616-890210247499") - )) + ("~ !\"#$%&'()*+,-./012345678" #uuid "093B7461-98EF-55DC-8616-890210247499"))) (deftest check-v5-null-ns-cases (testing "v5 null-ns case-based correctness..." (doseq [case +v5-null-ns-cases+] - (is (= (second case) (v5 +null+ (first case))))))) - + (is (= (second case) (uuid/v5 uuid/+null+ (first case))))))) (def +v5-dns-ns-cases+ @@ -231,7 +226,7 @@ (deftest check-v5-dns-ns-cases (testing "v5 dns-ns case-based correctness..." (doseq [case +v5-dns-ns-cases+] - (is (= (second case) (v5 +namespace-dns+ (first case))))))) + (is (= (second case) (uuid/v5 uuid/+namespace-dns+ (first case))))))) (def +v5-oid-ns-cases+ @@ -336,4 +331,4 @@ (deftest check-v5-oid-ns-cases (testing "v5 oid-ns case-based correctness..." (doseq [case +v5-oid-ns-cases+] - (is (= (second case) (v5 +namespace-oid+ (first case))))))) + (is (= (second case) (uuid/v5 uuid/+namespace-oid+ (first case))))))) diff --git a/test/clj_uuid/v6_test.clj b/test/clj_uuid/v6_test.clj index 85bb308..132b938 100644 --- a/test/clj_uuid/v6_test.clj +++ b/test/clj_uuid/v6_test.clj @@ -1,16 +1,16 @@ (ns clj-uuid.v6-test - "Time based UUIDs tests" - (:require [clojure.test :refer :all] - [clojure.set] - [clj-uuid :as uuid :refer [v6 get-timestamp]] - [clj-uuid.clock :as clock])) + "Time based UUID tests" + (:require [clj-uuid.clock :as clock] + [clj-uuid.core :as uuid] + [clojure.set :as set] + [clojure.test :refer :all])) (deftest check-v6-single-threaded (let [iterations 1000000 groups 10] (testing "single-thread v6 uuid uniqueness..." (dotimes [_ groups] - (let [result (repeatedly iterations v6)] + (let [result (repeatedly iterations uuid/v6)] (is (= (count result) (count (set result))))))))) (deftest check-v6-concurrency @@ -19,13 +19,13 @@ agents (map agent (repeat concur nil)) working (map #(send-off % (fn [state] - (repeatedly extent v6))) + (repeatedly extent uuid/v6))) agents) _ (apply await working) answers (map deref working)] (testing (str "concurrent v6 uuid uniqueness (" concur " threads)...") (is (= (* concur extent) - (count (apply clojure.set/union (map set answers)))))) + (count (apply set/union (map set answers)))))) (testing (str "concurrent v6 monotonic increasing (" concur " threads)...") (is (every? identity (map (partial apply uuid/<) answers))))))) @@ -33,5 +33,5 @@ (dotimes [_ 1000000] (let [time (clock/monotonic-time)] (with-redefs [clock/monotonic-time (constantly time)] - (is (= time (uuid/get-timestamp (v6))) + (is (= time (uuid/get-timestamp (uuid/v6))) "Timestamp should be retrievable from v6 UUID"))))) diff --git a/test/clj_uuid/v7_test.clj b/test/clj_uuid/v7_test.clj index 286fd71..914916d 100644 --- a/test/clj_uuid/v7_test.clj +++ b/test/clj_uuid/v7_test.clj @@ -1,15 +1,15 @@ (ns clj-uuid.v7-test - (:require [clojure.test :refer :all] - [clojure.set] - [clj-uuid :as uuid :refer [v7]] - [clj-uuid.clock :as clock])) + (:require [clj-uuid.clock :as clock] + [clj-uuid.core :as uuid] + [clojure.set :as set] + [clojure.test :refer :all])) (deftest check-v7-single-threaded (let [iterations 1000000 groups 10] (testing "single-thread v7 uuid uniqueness..." (dotimes [_ groups] - (let [result (repeatedly iterations v7)] + (let [result (repeatedly iterations uuid/v7)] (is (= (count result) (count (set result))))))))) (deftest check-v7-concurrency @@ -18,13 +18,13 @@ agents (map agent (repeat concur nil)) working (map #(send-off % (fn [state] - (repeatedly extent v7))) + (repeatedly extent uuid/v7))) agents) _ (apply await working) answers (map deref working)] (testing (str "concurrent v7 uuid uniqueness (" concur " threads)...") (is (= (* concur extent) - (count (apply clojure.set/union (map set answers)))))) + (count (apply set/union (map set answers)))))) (testing (str "concurrent v7 monotonic increasing (" concur " threads)...") (is (every? identity @@ -34,5 +34,5 @@ (dotimes [_ 1000000] (let [time (first (clock/monotonic-unix-time-and-random-counter))] (with-redefs [clock/monotonic-unix-time-and-random-counter (constantly [time (rand-int 4095)])] - (is (= time (uuid/get-timestamp (v7))) + (is (= time (uuid/get-timestamp (uuid/v7))) "Timestamp should be retrievable from v7 UUID"))))) diff --git a/test/clj_uuid/v8_test.clj b/test/clj_uuid/v8_test.clj index 6fe3fd7..77f5994 100644 --- a/test/clj_uuid/v8_test.clj +++ b/test/clj_uuid/v8_test.clj @@ -1,13 +1,12 @@ (ns clj-uuid.v8-test "Custom UUIDs tests" - (:refer-clojure :exclude [uuid? max]) - (:require [clojure.test :refer :all] - [clj-uuid :refer :all :exclude [> < =]])) + (:require [clj-uuid.core :as uuid] + [clojure.test :refer :all])) (deftest check-v8-special-cases (testing "v8 custom UUID" - (is (= (v8 0 0) #uuid "00000000-0000-8000-8000-000000000000")) - (is (= (v8 0 1) #uuid "00000000-0000-8000-8000-000000000001")) - (is (= (v8 0 -1) #uuid "00000000-0000-8000-bfff-ffffffffffff")) - (is (= (v8 -1 0) #uuid "ffffffff-ffff-8fff-8000-000000000000")) - (is (= (v8 -1 -1) #uuid "ffffffff-ffff-8fff-bfff-ffffffffffff")))) + (is (= (uuid/v8 0 0) #uuid "00000000-0000-8000-8000-000000000000")) + (is (= (uuid/v8 0 1) #uuid "00000000-0000-8000-8000-000000000001")) + (is (= (uuid/v8 0 -1) #uuid "00000000-0000-8000-bfff-ffffffffffff")) + (is (= (uuid/v8 -1 0) #uuid "ffffffff-ffff-8fff-8000-000000000000")) + (is (= (uuid/v8 -1 -1) #uuid "ffffffff-ffff-8fff-bfff-ffffffffffff"))))