Common Lisp [[https://en.wikipedia.org/wiki/Universally_unique_identifier][UUID]] library with zero dependencies
** Rationale
** Limitations
Please note that by default the implementation dependent built-in CL random number generator is used, which might not be of sufficient quality for your purposes. The use of cryptographic-quality random numbers is strongly recommended in order to reduce the probability of repeated values. Please see the section on randomness in this README for setting up an alternative source of random numbers especially for generating UUID versions 4 and 7.
You can also use the [[https://github.com/ak-coram/cl-frugal-uuid#non-frugal-setup][frugal-uuid/non-frugal]] system to change the defaults and provide support for generating name-based UUIDs (versions 3 and 5). This should only be necessary if you intend to securely generate new UUIDs via this library: for representing existing UUIDs (parsing, comparing and converting values) relying on the frugal-uuid system should be sufficient.
Please also see [[https://github.com/ak-coram/cl-frugal-uuid#Saving-a-Lisp-image][this section]] of the README if you intend on saving your program as a Lisp image.
The following implementations and operating systems are tested via [[https://github.com/ak-coram/cl-frugal-uuid/blob/main/.github/workflows/CI.yml][CI]]:
** Installation
cl-frugal-uuid can be installed via [[https://www.quicklisp.org/][Quicklisp]]:
(ql:quickload :frugal-uuid)
The latest version is available from the [[https://ultralisp.org/][Ultralisp]] distribution:
(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil) (ql:quickload :frugal-uuid)
Alternatively you can also rely on [[https://github.com/ocicl/ocicl][ocicl]].
** Basic usage
;; Generate version 1 UUID (fuuid:make-v1) ; => #<FRUGAL-UUID:UUID 58c3e000-1875-100c-a524-89154ef00c1c>
;; Generate random UUID (fuuid:make-v4) ; => #<FRUGAL-UUID:UUID 3ffc05ba-9c35-4f21-8535-beba03a2495c>
;; Convert random UUID to canonical string representation (fuuid:to-string (fuuid:make-v4)) ; => "2172e412-06a6-4cfb-bbf1-3584aadaed15"
;; Parse UUID from string (fuuid:from-string "0909e4f4-8333-4712-8609-5ae02d735772") ;; => #<FRUGAL-UUID:UUID 0909e4f4-8333-4712-8609-5ae02d735772>
;; Convert UUID to octets (fuuid:to-octets (fuuid:make-v4)) ;; => #(33 194 68 252 59 137 78 19 177 22 25 166 226 241 44 55)
;; Compare two random UUID values (fuuid:uuid= (fuuid:make-v4) (fuuid:make-v4)) ; => NIL
;; Loosely compare UUID with canonical string representation (fuuid:uuid-equal-p (fuuid:from-string "0909e4f4-8333-4712-8609-5ae02d735772") "0909e4f4-8333-4712-8609-5ae02d735772") ; => T
** Embedding UUIDs
You can efficiently embed UUID literals in your source using the ~ macro:
(fuuid:~ 1d2f8148-c30c-4f9d-8aae-2ac5807000dc) ;; => #<FRUGAL-UUID:UUID 1d2f8148-c30c-4f9d-8aae-2ac5807000dc>
(multiple-value-list (fuuid:~ 1d2f8148-c30c-4f9d-8aae-2ac5807000dc
fa23520b-972c-4f84-b5bf-91f0d7d4f4b4))
;; => (#<FRUGAL-UUID:UUID 1d2f8148-c30c-4f9d-8aae-2ac5807000dc>
;; #
;; When called without arguments, the macro generates a random
;; value during expansion (via MAKE-V4):
(fuuid:~)
;; => #
For writing your own macros, you can rely on the COMPILE-LITERAL function.
** non-frugal setup
(ql:quickload :frugal-uuid/non-frugal)
The above system is provided to conveniently setup the following:
frugal-uuid/non-frugal is composed of multiple systems which can also be loaded individually if you only require parts of the above. See [[https://github.com/ak-coram/cl-frugal-uuid/blob/main/frugal-uuid.asd][frugal-uuid.asd]] for details.
** UUID Versions
*** Version 1
Node ID and clock sequence are initialized randomly by default, but you can provide your own values (or even your own function for generating timestamp values) using MAKE-V1-GENERATOR. Currently there's no mechanism included in this library for determining the systems MAC address, but the PARSE-NODE-ID function is included for parsing it once obtained.
The clock sequence is reinitialized on every new clock tick with the highest bit set to zero and a random value for the remaining bits.
To avoid repeated values, it is recommended for multithreaded applications to use a separate generator for each thread. This is automatically done using bordeaux-threads if you use the frugal-uuid/non-frugal system.
Please also see the section on randomness for setting up alternative sources for random numbers.
(bordeaux-threads-2:make-thread (lambda () (format t "~A" (fuuid:make-v1))) :initial-bindings `((fuuid:v1-generator . ,(fuuid:make-v1-generator))))
*** Version 2
Generating "DCE security" UUIDs (version 2) is not implemented.
*** Version 3
See section for version 5 below.
*** Version 4
;; Generate random UUID (fuuid:make-v4)
;; Provide 128-bit random number directly and set the bits for version 4 (fuuid:make-v4-from-integer (secure-random:number #xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
*** Version 5
If you're using the frugal-uuid/non-frugal system, then you can also generate name-based (version 3 or version 5) UUIDs:
(fuuid:make-v3 fuuid:ns-url "https://html5zombo.com/")
;; => #
(let ((cheese-namespace (fuuid:make-v4)))
(fuuid:make-v5 cheese-namespace "Orda"))
;; => #
*** Version 6
The implementation first generates a version 1 UUID (see above) and reorders the timestamp fields to create a version 6 UUID value. Please note that the slot accessors for the UUID class (TIME-LOW, TIME-MID and TIME-HI-AND-VERSION) are based on RFC 4122 and do not match the timestamp part names used for version 6. Please rely on the following functions instead:
(fuuid:make-v6) ;; => #<FRUGAL-UUID:UUID 1ee2d53a-729c-6929-8c8c-e780ff7b0e6a>
*** Version 7
When an accurate system clock is available (see frugal-uuid/non-frugal):
This approach is a combination of methods 2 and 3 from the specification for providing monotonicity.
When no accurate clock is available:
This corresponds to method 2 from the specification for providing monotonicity.
Regarding the monotonic random counter:
(fuuid:make-v7) ;; => #<UUID 0189a085-b115-7e5d-8664-3c5fbe542e4c>
*** Version 8
The MAKE-V8 function is provided to create version 8 UUID values from specifying the three custom components:
**** MiNaRa UUID (custom UUID relying on version 8)
The frugal-uuid/non-frugal system provides a custom UUID generation scheme which encodes a nanosecond precision timestamp in a version 8 UUID and randomizes the remaining variable bits. It consists of three components which also make up the name:
The random component may also be used for encoding custom data.
(fuuid:make-minara) ;; => #<FRUGAL-UUID:UUID 01899a63-6540-8d89-b9db-f0fa388bf86e>
(fuuid:minara-components *) ;; => 1690512352576 (41 bits, #x1899A636540) ;; 887271 (20 bits, #xD89E7) ;; 7864781852375150 (53 bits, #x1BF0FA388BF86E)
(fuuid:minara-components (fuuid:make-minara 42)) ;; => 1690514402692 (41 bits, #x1899A82AD84) ;; 507614 (19 bits, #x7BEDE) ;; 42 (6 bits, #x2A, #o52, #b101010)
** Timestamps
The Common Lisp standard only provides a function to retrieve the current wall-clock time as a number of whole seconds elapsed since the Common Lisp epoch. In order to make use of the subsecond bits of the timestamp (in UUID versions 1, 6 and 7), the default implementation uses them as a counter which is incremented every time the clock sequence values are exhausted within the same clock tick. If the total number of unique values is exhausted, the counter wraps around and starts at zero again.
Within the frugal-uuid/non-frugal system a more accurate clock is available and the above doesn't apply.
** Randomness
If you have an alternative source of random numbers, you can use it instead of the built-in random number generator. Please consult the documentation of your chosen implementation or library for details on thread-safety if you intend to use this in a multi-threaded program.
*** Ironclad
A setup using [[https://github.com/sharplispers/ironclad#pseudo-random-number-generation][Ironclad PRNG]]:
(ql:quickload :ironclad/prngs)
;; Use the default Ironclad PRNG: (fuuid:initialize-random #'crypto:strong-random)
;; Setup with custom PRNG: (fuuid:initialize-random #'crypto:strong-random (lambda () (ironclad:make-prng :os)))
;; Dynamically bind the generator: (fuuid:with-random-number-generator (ironclad:make-prng :os) (fuuid:make-v4))
*** secure-random
Below you'll find and example using the [[https://github.com/avodonosov/secure-random][secure-random]] library which relies on OpenSSL:
;; Load library for generating secure random numbers (ql:quickload :secure-random)
;; Dynamically bind both random number generator & random function: (fuuid:with-random (#'secure-random:number secure-random:generator) (fuuid:make-v4))
*** Saving a Lisp image
If you generate UUID values while building your Lisp image, it can include global random state which already has been initialized. This would mean that executing the image multiple times could lead to generating repeated UUID values.
To avoid this, you can clear the global state before saving your image or on image startup (it will be reinitialized on first use):
(setf fuuid:*random-number-generator* nil
fuuid:*v1-generator* nil
fuuid:*v7-generator* nil)
If you only load the systems in this project this should not be an issue as the global random state is initialized on first use (when generating UUID values of either version 1 or version 4).
Here's an example session illustrating the issue:
$ sbcl
(ql:quickload :frugal-uuid) To load "frugal-uuid": Load 1 ASDF system: frugal-uuid ; Loading "frugal-uuid" (:FRUGAL-UUID)
(fuuid:make-v4)
(ql:quickload :trivial-dump-core) To load "trivial-dump-core": Load 1 ASDF system: trivial-dump-core ; Loading "trivial-dump-core"
(:TRIVIAL-DUMP-CORE)
(trivial-dump-core:save-executable "echo-random-uuid" (lambda () (format t "~a~%" (fuuid:to-string (fuuid:make-v4))))) [undoing binding stack and other enclosing state... done] [performing final GC... done] [defragmenting immobile space... (inst,fdefn,code,sym)=959+18456+19452+26866... done] [saving current Lisp image into echo-random-uuid: writing 3376 bytes from the static space at 0x50000000 writing 21266432 bytes from the dynamic space at 0x1000000000 writing 7443312 bytes from the read-only space at 0xfff8e0000 writing 2015232 bytes from the fixedobj space at 0x50100000 writing 11993088 bytes from the text space at 0x52a00000 done]
$ ./echo-random-uuid cb09eb20-64c6-4ed0-b5be-c89388a673fe $ ./echo-random-uuid cb09eb20-64c6-4ed0-b5be-c89388a673fe
** Running tests
(ql:quickload :frugal-uuid/test)
;; Using ASDF: (asdf:test-system :frugal-uuid) ;; Using FiveAM directly: (fiveam:run! :frugal-uuid)
** Legal