Coverage report: /home/ellis/comp/core/lib/obj/uuid.lisp
Kind | Covered | All | % |
expression | 162 | 398 | 40.7 |
branch | 9 | 28 | 32.1 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
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
(defun uuid-to-string (uuid)
235
(print-object uuid nil))
237
(defmacro arr-to-bytes (from to array)
238
"Helper macro used in byte-array-to-uuid."
239
`(loop for i from ,from to ,to
241
do (setf (ldb (byte 8 (* 8 (- ,to i))) res) (aref ,array i))
242
finally (return res)))
244
(defun octet-vector-to-uuid (array)
245
"Converts a byte-array generated with uuid-to-byte-array to an uuid."
247
(array (unsigned-byte 8) (16))
248
"Provided value is not an one-dimensional array with 16 elements of type (unsigned-byte 8)")
250
:time-low (arr-to-bytes 0 3 array)
251
:time-mid (arr-to-bytes 4 5 array)
252
:time-high (arr-to-bytes 6 7 array)
253
:clock-seq-var (aref array 8)
254
:clock-seq-low (aref array 9)
255
:node (arr-to-bytes 10 15 array)))
257
(defun digest-uuid (digest uuid name)
258
"Helper function that produces a digest from a namespace (a byte array) and a string. Used for the
259
generation of version 3 and 5 uuids."
260
(declare (ignorable digest uuid name))
262
(let ((digester (ironclad:make-digest digest)))
263
(ironclad:update-digest digester uuid)
264
(ironclad:update-digest digester (trivial-utf-8:string-to-utf-8-bytes name))
265
(ironclad:produce-digest digester)))