Termite currently has a dependency on libuuid to generate universally unique identifiers. Since there is no need for the result to conform to the uuid_t data type, just that the result is unique across space and time, I wrote a uuid generator in Scheme which will make running Termite easier on more platforms. I loosely followed the pattern for generating primary keys in EJB Design Patterns which does not require a singleton or a database, etc. i.e. multiple instances of this uuid maker can exist in the same address space or in different address spaces, and on different hosts. I had fun replacing the absolutely horrible C code from libuuid with short, readable, Scheme.
The result is a 128-bit unsigned integer but the only requirement is that it is a unique Scheme object across space and time (within a reasonable amount of certainty). The code is actually following the convention for uuid's, i.e. display each 32-bit part in hex separated by dashes.
> (load "my-uuid.scm")
"/home/pdlogan/dev/termite/my-uuid.scm"
> (define make-uuid (make-uuid-maker))
> (make-uuid)
"1A10E03B-4FF3486B-00000057-F7CA93A2"
Each call to make-uuid-maker returns a new closure around an "address part" and an "object part". Every uuid this closure makes will be unique to its IP address and unique to the internal id of the object in the specific Scheme runtime. (Note: the original uuid generators use the network card MAC address but the IP address is easier to get and just as good for this purpose.)
Each call to one of these closures combines its address part and object part to a "time part" and a "random part". The time part is roughly the number of milliseconds since January 1, 1970 mod (2^32)-1. The random part is a random 32-bit unsigned integer. So within the closure's IP address and randomly allocated object, each resulting uuid is also made unique to the millisecond and a random number *within* the millisecond.
(define (make-uuid-maker)
(let ((address-part (or (hex-encode-ip-address)
(hex-encode-random-integer)))
(object-id-part (hex-encode-random-object-id)))
(lambda ()
(let ((time-part (hex-encode-current-milliseconds))
(random-part (hex-encode-random-integer)))
(string-append time-part "-" address-part "-" object-id-part "-" random-part)))))
The IP address is the host's internet address if it has one, or an intranet address, otherwise the address part becomes just another 32-bit random integer.
(define (hex-encode-ip-address)
(let* ((address (or (get-internet-address)
(get-intranet-address))))
(if address
(lengthen-with-leading-zeros
(number->string (string->number
(let ((result ""))
(for-each
(lambda (n)
(set! result (string-append result (number->string n))))
(u8vector->list address))
result))
16))
#f)))
The object id closed within a uuid maker is also random. If a closure is always created at a specific point, e.g. in initialization, then this object runs the risk of having the same id each time. Instead a random number of objects (not more than 100) are created before the one captured in the closure.
Well that was fun. If you see any serious flaws let me know. There are some Gambit-specific dependencies here but nothing too difficult to replace. The formatting's really bad, but I am off to the beach for the day so later.
(define (hex-encode-random-object-id)
(let loop ((n (random-integer 100)))
(let ((id (object->serial-number (cons 1 2))))
(if (= n 0)
(lengthen-with-leading-zeros (number->string id 16))
(loop (- n 1))))))