Mercurial > core / lisp/lib/obj/uuid.lisp
changeset 698: |
96958d3eb5b0 |
parent: |
9eb2c112aa16
|
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Fri, 04 Oct 2024 22:04:59 -0400 |
permissions: |
-rw-r--r-- |
description: |
fixes |
1 ;;; obj/uuid.lisp --- UUIDs 9 "Holds the clock sequence. It is set when a version 1 uuid is 10 generated for the first time and remains unchanged during a whole 14 "Holds the IEEE 802 MAC address or a random number when such is not 17 (defvar *ticks-per-count* 1024 18 "Holds the amount of ticks per count. The ticks per count determine 19 the number of possible version 1 uuids created for one time 20 interval. Common Lisp provides INTERNAL-TIME-UNITS-PER-SECOND which 21 gives the ticks per count for the current system so *ticks-per-count* 22 can be set to INTERNAL-TIME-UNITS-PER-SECOND") 24 (defparameter *uuid-random-state* nil 25 "Holds the random state used for generation of random numbers") 28 ((time-low :initarg :time-low 29 :type (unsigned-byte 32) 32 (time-mid :initarg :time-mid 33 :type (unsigned-byte 16) 36 (time-high-and-version :initarg :time-high 37 :type (unsigned-byte 16) 40 (clock-seq-and-reserved :initarg :clock-seq-var 41 :type (unsigned-byte 8) 42 :accessor clock-seq-var 44 (clock-seq-low :initarg :clock-seq-low 45 :type (unsigned-byte 8) 46 :accessor clock-seq-low 49 :type (unsigned-byte 48) 52 (:documentation "Represents an uuid")) 54 (defun make-uuid-from-string (string) 55 "Creates an uuid from the string represenation of an uuid. (example input string 6ba7b810-9dad-11d1-80b4-00c04fd430c8)" 56 (unless (= (length string) 36) 57 (error "~@<Could not parse ~S as UUID: string representation ~ 58 has invalid length (~D). A valid UUID string representation has 36 ~ 59 characters.~@:>" string (length string))) 60 (unless (and (eq (aref string 8) #\-) 61 (eq (aref string 13) #\-) 62 (eq (aref string 18) #\-) 63 (eq (aref string 23) #\-)) 64 (error "~@<Could not parse ~S as UUID: positions 8, ~ 65 13, 18, 21 and 23 have to contain ~C (~A) characters.~@:>" 66 string #\- (char-name #\-))) 67 (labels ((parse-block (string start end) 68 (parse-integer string :start start :end end :radix 16))) 70 :time-low (parse-block string 0 8) 71 :time-mid (parse-block string 9 13) 72 :time-high (parse-block string 14 18) 73 :clock-seq-var (parse-block string 19 21) 74 :clock-seq-low (parse-block string 21 23) 75 :node (parse-block string 24 36)))) 77 (defparameter +namespace-dns+ (make-uuid-from-string "6ba7b810-9dad-11d1-80b4-00c04fd430c8") 78 "The DNS Namespace. Can be used for the generation of uuids version 3 and 5") 79 (defparameter +namespace-url+ (make-uuid-from-string "6ba7b811-9dad-11d1-80b4-00c04fd430c8") 80 "The URL Namespace. Can be used for the generation of uuids version 3 and 5") 81 (defparameter +namespace-oid+ (make-uuid-from-string "6ba7b812-9dad-11d1-80b4-00c04fd430c8") 82 "The OID Namespace. Can be used for the generation of uuids version 3 and 5") 83 (defparameter +namespace-x500+ (make-uuid-from-string "6ba7b814-9dad-11d1-80b4-00c04fd430c8") 84 "The x500+ Namespace. Can be used for the generation of uuids version 3 and 5") 87 "Get MAC address of first ethernet device" 90 (let ((interface (first (remove "lo" 91 (mapcan (lambda (x) (last (pathname-directory x))) 92 (directory "/sys/class/net/*/address")) 94 (when (not (null interface)) 95 (with-open-file (address (make-pathname :directory 96 `(:absolute "sys" "class" "net" ,interface) 98 (parse-integer (remove #\: (read-line address)) :radix 16)))))) 100 (unless *uuid-random-state* 101 (setf *uuid-random-state* (make-random-state t))) 102 (setf node (dpb #b01 (byte 8 0) (random #xffffffffffff *uuid-random-state*)))) 105 (let ((uuids-this-tick 0) 107 (defun get-timestamp () 108 "Get timestamp, compensate nanoseconds intervals" 111 (let ((time-now (+ (* (get-universal-time) 10000000) 100103040000000000))) 112 ;10010304000 is time between 1582-10-15 and 1900-01-01 in seconds 113 (cond ((not (= last-time time-now)) 114 (setf uuids-this-tick 0 116 (return-from get-timestamp time-now)) 118 (cond ((< uuids-this-tick *ticks-per-count*) 119 (incf uuids-this-tick) 120 (return-from get-timestamp (+ time-now uuids-this-tick))) 125 (defun format-v3or5-uuid (hash ver) 126 "Helper function to format a version 3 or 5 uuid. Formatting means setting the appropriate version bytes." 127 (check-type ver (or (eql 3) (eql 5)) "either 3 or 5.") 129 (let ((result (octet-vector-to-uuid (subseq hash 0 16)))) 130 (setf (time-high result) (dpb (ecase ver 134 (logior (ash (aref hash 6) 8) 136 (clock-seq-var result) (dpb #b10 (byte 2 6) (aref hash 8))) 139 (defmethod print-object ((id uuid) stream) 140 "Prints an uuid in the string represenation of an uuid. (example string 6ba7b810-9dad-11d1-80b4-00c04fd430c8)" 141 (format stream "~8,'0X-~4,'0X-~4,'0X-~2,'0X~2,'0X-~12,'0X" 149 (defun print-bytes (stream uuid) 150 "Prints the raw bytes in hex form. (example output 6ba7b8109dad11d180b400c04fd430c8)" 151 (format stream "~8,'0X~4,'0X~4,'0X~2,'0X~2,'0X~12,'0X" 159 (defun format-as-urn (stream uuid) 160 "Prints the uuid as a urn" 161 (format stream "urn:uuid:~(~A~)" uuid)) 163 (defun make-null-uuid () 164 "Generates a NULL uuid (i.e 00000000-0000-0000-0000-000000000000)" 165 (make-instance 'uuid)) 167 (defun make-v1-uuid () 168 "Generates a version 1 (time-based) uuid." 169 (unless *uuid-random-state* 170 (setf *uuid-random-state* (make-random-state t))) 171 (let ((timestamp (get-timestamp))) 172 (when (zerop *clock-seq*) 173 (setf *clock-seq* (random 10000 *uuid-random-state*))) 175 (setf *node* (get-node-id))) 177 :time-low (ldb (byte 32 0) timestamp) 178 :time-mid (ldb (byte 16 32) timestamp) 179 :time-high (dpb #b0001 (byte 4 12) (ldb (byte 12 48) timestamp)) 180 :clock-seq-var (dpb #b10 (byte 2 6) (ldb (byte 6 8) *clock-seq*)) 181 :clock-seq-low (ldb (byte 8 0) *clock-seq*) 184 (defun make-v3-uuid (namespace name) 185 "Generates a version 3 (named based MD5) uuid." 187 (digest-uuid :md5 (uuid-to-octet-vector namespace) name) 190 (defun make-v4-uuid () 191 "Generates a version 4 (random) uuid" 192 (unless *uuid-random-state* 193 (setf *uuid-random-state* (make-random-state t))) 195 :time-low (random #xffffffff *uuid-random-state*) 196 :time-mid (random #xffff *uuid-random-state*) 197 :time-high (dpb #b0100 (byte 4 12) (ldb (byte 12 0) (random #xffff *uuid-random-state*))) 198 :clock-seq-var (dpb #b10 (byte 2 6) (ldb (byte 8 0) (random #xff *uuid-random-state*))) 199 :clock-seq-low (random #xff *uuid-random-state*) 200 :node (random #xffffffffffff *uuid-random-state*))) 202 (defun make-v5-uuid (namespace name) 203 "Generates a version 5 (name based SHA1) uuid." 205 (digest-uuid :sha1 (uuid-to-octet-vector namespace) name) 208 (defun uuid= (uuid1 uuid2) 210 (and (= (time-low uuid1) (time-low uuid2)) 211 (= (time-mid uuid1) (time-mid uuid2)) 212 (= (time-high uuid1) (time-high uuid2)) 213 (= (clock-seq-var uuid1) (clock-seq-var uuid2)) 214 (= (clock-seq-low uuid1) (clock-seq-low uuid2)) 215 (= (node uuid1)(node uuid2))))) 217 (defun uuid-to-octet-vector (uuid) 218 "Converts an uuid to byte-array" 219 (let ((array (make-array 16 :element-type '(unsigned-byte 8)))) 220 (with-slots (time-low time-mid time-high-and-version clock-seq-and-reserved clock-seq-low node) 222 (loop for i from 3 downto 0 223 do (setf (aref array (- 3 i)) (ldb (byte 8 (* 8 i)) time-low))) 224 (loop for i from 5 downto 4 225 do (setf (aref array i) (ldb (byte 8 (* 8 (- 5 i))) time-mid))) 226 (loop for i from 7 downto 6 227 do (setf (aref array i) (ldb (byte 8 (* 8 (- 7 i))) time-high-and-version))) 228 (setf (aref array 8) (ldb (byte 8 0) clock-seq-and-reserved)) 229 (setf (aref array 9) (ldb (byte 8 0) clock-seq-low)) 230 (loop for i from 15 downto 10 231 do (setf (aref array i) (ldb (byte 8 (* 8 (- 15 i))) node))) 234 (defmacro arr-to-bytes (from to array) 235 "Helper macro used in byte-array-to-uuid." 236 `(loop for i from ,from to ,to 238 do (setf (ldb (byte 8 (* 8 (- ,to i))) res) (aref ,array i)) 239 finally (return res))) 241 (defun octet-vector-to-uuid (array) 242 "Converts a byte-array generated with uuid-to-byte-array to an uuid." 244 (array (unsigned-byte 8) (16)) 245 "Provided value is not an one-dimensional array with 16 elements of type (unsigned-byte 8)") 247 :time-low (arr-to-bytes 0 3 array) 248 :time-mid (arr-to-bytes 4 5 array) 249 :time-high (arr-to-bytes 6 7 array) 250 :clock-seq-var (aref array 8) 251 :clock-seq-low (aref array 9) 252 :node (arr-to-bytes 10 15 array))) 254 (defun digest-uuid (digest uuid name) 255 "Helper function that produces a digest from a namespace (a byte array) and a string. Used for the 256 generation of version 3 and 5 uuids." 257 (declare (ignorable digest uuid name)) 259 (let ((digester (ironclad:make-digest digest))) 260 (ironclad:update-digest digester uuid) 261 (ironclad:update-digest digester (trivial-utf-8:string-to-utf-8-bytes name)) 262 (ironclad:produce-digest digester)))