#lang scheme
(require (planet soegaard/digest/digest)
(planet "host-serial.scm" ("oesterholt" "host-serial.plt")))
(define (char-hexadecimal? char)
(or (char<=? #\0 char #\9)
(char<=? #\a char #\f)
(char<=? #\A char #\F)))
(define (nibble? x)
(and (integer? x)
(exact? x)
(<= 0 x 15)))
(define (hex-string? x)
(and (string? x)
(let/ec exit
(for ((char (in-string x)))
(unless (char-hexadecimal? char)
(exit #f)))
#t)))
(define (hex-digit->nibble char)
(cond ((char<=? #\0 char #\9)
(- (char->integer char) (char->integer #\0)))
((char<=? #\a char #\f)
(+ (- (char->integer char) (char->integer #\a)) 10))
((char<=? #\A char #\F)
(+ (- (char->integer char) (char->integer #\A)) 10))
(else
(error 'hex-digit->nibble
"expected a hexadecimal digit, given ~a" char))))
(define hex-digits
#(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\a #\b #\c #\d #\e #\f))
(define (byte->hex-string byte)
(let-values (((q r) (quotient/remainder byte 16)))
(string (vector-ref hex-digits q) (vector-ref hex-digits r))))
(define (hex-string->byte hex-string)
(unless (= (string-length hex-string) 2)
(error 'hex-string->byte
"expected a string of heximal digits of length 2, given ~a"
hex-string))
(+ (* (hex-digit->nibble (string-ref hex-string 0)) 16)
(hex-digit->nibble (string-ref hex-string 1))))
(define (hex-string->bytes hex-string)
(unless (even? (string-length hex-string))
(error 'hex-string->bytes
"expected an even length string of hexidecimal characters, given ~a"
hex-string))
(apply bytes (for/list ((i (in-range 0 (string-length hex-string) 2)))
(hex-string->byte (substring hex-string i (+ i 2))))))
(define-struct uuid
(bytes)
#:property prop:custom-write
(lambda (uuid port write?)
(write-string (format "#<uuid ~a>" (uuid->string uuid)) port)))
(define (uuid-RFC-4122? uuid)
(= (bitwise-and (bytes-ref (uuid-bytes uuid) 8) #xc0) #x80))
(define (uuid-version uuid)
(bitwise-bit-field (bytes-ref (uuid-bytes uuid) 6) 4 8))
(define (uuid->bytes uuid)
(uuid-bytes uuid))
(define (uuid-bytes<->uuid-network-bytes uuid-bytes)
(bytes
(bytes-ref uuid-bytes 3)
(bytes-ref uuid-bytes 2)
(bytes-ref uuid-bytes 1)
(bytes-ref uuid-bytes 0)
(bytes-ref uuid-bytes 5)
(bytes-ref uuid-bytes 4)
(bytes-ref uuid-bytes 7)
(bytes-ref uuid-bytes 6)
(bytes-ref uuid-bytes 8)
(bytes-ref uuid-bytes 9)
(bytes-ref uuid-bytes 10)
(bytes-ref uuid-bytes 11)
(bytes-ref uuid-bytes 12)
(bytes-ref uuid-bytes 13)
(bytes-ref uuid-bytes 14)
(bytes-ref uuid-bytes 15)))
(define (hex-string->uuid hex-string)
(unless (= (string-length hex-string) 32)
(error 'hex-string->uuid
"expected a string of hexidecimal characters of length 32, given ~a"
hex-string))
(make-uuid (hex-string->bytes hex-string)))
(define (uuid->hex-string uuid)
(bytes->hex-string (uuid-bytes uuid)))
(define (uuid->string uuid)
(let ((bytes (uuid-bytes uuid)))
(string-append
(bytes->hex-string (subbytes bytes 0 4))
"-"
(bytes->hex-string (subbytes bytes 4 6))
"-"
(bytes->hex-string (subbytes bytes 6 8))
"-"
(bytes->hex-string (subbytes bytes 8 10))
"-"
(bytes->hex-string (subbytes bytes 10 16)))))
(define (uuid->urn-string uuid)
(string-append "urn:uuid:" (uuid->string uuid)))
(define uuid-string-regexp
#px"^(?i:(urn:uuid:))?([[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$")
(define (uuid-string? x)
(and (string? x)
(regexp-match? uuid-string-regexp x)))
(define (string->uuid string)
(let ((match (regexp-match uuid-string-regexp string)))
(unless match
(error 'string->uuid
"expected a valid uuid string, given ~s" string))
(let* ((uuid-string (third match))
(hex-string (string-append
(substring uuid-string 0 8)
(substring uuid-string 9 13)
(substring uuid-string 14 18)
(substring uuid-string 19 23)
(substring uuid-string 24 36))))
(hex-string->uuid hex-string))))
(define (current-RFC-4122-time)
(+ 122192928000000000
(* (inexact->exact (current-inexact-milliseconds)) 10000)))
(define (make-uuid-1)
(let* ((uuid-bytes (make-bytes 16))
(time (current-RFC-4122-time))
(time-low (bitwise-bit-field time 0 32))
(time-mid (bitwise-bit-field time 32 48))
(time-hi (bitwise-bit-field time 48 64))
(mac (host-primary-serial-number)))
(integer->integer-bytes time-low 4 #f #t uuid-bytes 0)
(integer->integer-bytes time-mid 2 #f #t uuid-bytes 4)
(integer->integer-bytes time-hi 2 #f #t uuid-bytes 6)
(bytes-set! uuid-bytes 8 (random 256))
(bytes-set! uuid-bytes 9 (random 256))
(if mac
(for ((i (in-range 6)))
(let* ((start (* i 3))
(end (+ start 2)))
(bytes-set! uuid-bytes (+ i 10) (hex-string->byte (substring mac start end)))))
(begin
(for ((i (in-range 10 16)))
(bytes-set! uuid-bytes i (random 256)))
(bytes-set!
uuid-bytes 10
(bitwise-ior (bytes-ref uuid-bytes 10) #x01))))
(bytes-set!
uuid-bytes 8
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 8) #x3f) #x80))
(bytes-set!
uuid-bytes 6
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 6) #x0f) #x10))
(make-uuid uuid-bytes)))
(define (uuid-1->date uuid)
(unless (and (uuid-RFC-4122? uuid)
(= (uuid-version uuid) 1))
(error 'uuid-1->date
"expected a time-based (version 1) RFC 4122 UUID, given ~a"
uuid))
(let* ((uuid-bytes (uuid-bytes uuid))
(time-low-bytes (subbytes uuid-bytes 0 4))
(time-mid-bytes (subbytes uuid-bytes 4 6))
(time-hi-bytes (subbytes uuid-bytes 6 8)))
(bytes-set!
time-hi-bytes 0
(bitwise-and (bytes-ref time-hi-bytes 0) #x0f))
(let* ((time (+ (* (integer-bytes->integer time-hi-bytes #f #t) 281474976710656)
(* (integer-bytes->integer time-mid-bytes #f #t) 4294967296)
(integer-bytes->integer time-low-bytes #f #t)))
(inexact-milliseconds (round (/ (- time 122192928000000000) 10000.0)))
(milliseconds (inexact->exact inexact-milliseconds)))
(seconds->date (quotient milliseconds 1000)))))
(define (make-uuid-3 namespace-uuid name #:legacy (force? #f))
(let* ((namespace-bytes
(if force?
(uuid-bytes<->uuid-network-bytes (uuid->bytes namespace-uuid))
(uuid->bytes namespace-uuid)))
(name-bytes (string->bytes/utf-8 name))
(namespace-name-bytes (bytes-append namespace-bytes name-bytes))
(hash (md5 namespace-name-bytes))
(hash-bytes (hex-string->bytes hash))
(uuid-bytes
(if force?
(uuid-bytes<->uuid-network-bytes hash-bytes)
hash-bytes)))
(bytes-set!
uuid-bytes 8
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 8) #x3f) #x80))
(bytes-set!
uuid-bytes 6
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 6) #x0f) #x30))
(make-uuid uuid-bytes)))
(define (make-uuid-4)
(let ((random-bytes (apply bytes (for/list ((i (in-range 16)))
(random 256)))))
(bytes-set!
random-bytes 8
(bitwise-ior (bitwise-and (bytes-ref random-bytes 8) #x3f) #x80))
(bytes-set!
random-bytes 6
(bitwise-ior (bitwise-and (bytes-ref random-bytes 6) #x0f) #x40))
(make-uuid random-bytes)))
(define (make-uuid-5 namespace-uuid name #:legacy (force? #f))
(let* ((namespace-bytes
(if force?
(uuid-bytes<->uuid-network-bytes (uuid->bytes namespace-uuid))
(uuid->bytes namespace-uuid)))
(name-bytes (string->bytes/utf-8 name))
(namespace-name-bytes (bytes-append namespace-bytes name-bytes))
(hash (sha1 namespace-name-bytes))
(hash-bytes (hex-string->bytes hash))
(uuid-bytes
(if force?
(uuid-bytes<->uuid-network-bytes hash-bytes)
hash-bytes)))
(bytes-set!
uuid-bytes 8
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 8) #x3f) #x80))
(bytes-set!
uuid-bytes 6
(bitwise-ior (bitwise-and (bytes-ref uuid-bytes 6) #x0f) #x50))
(make-uuid uuid-bytes)))
(define (uuid=? uuid-1 uuid-2)
(bytes=? (uuid->bytes uuid-1) (uuid->bytes uuid-2)))
(define (uuid<? uuid-1 uuid-2)
(bytes<? (uuid->bytes uuid-1) (uuid->bytes uuid-2)))
(define (uuid>? uuid-1 uuid-2)
(bytes>? (uuid->bytes uuid-1) (uuid->bytes uuid-2)))
(define nil-uuid (hex-string->uuid "00000000000000000000000000000000"))
(define namespace-DNS (hex-string->uuid "6ba7b8109dad11d180b400c04fd430c8"))
(define namespace-URL (hex-string->uuid "6ba7b8119dad11d180b400c04fd430c8"))
(define namespace-OID (hex-string->uuid "6ba7b8129dad11d180b400c04fd430c8"))
(define namespace-X500 (hex-string->uuid "6ba7b8149dad11d180b400c04fd430c8"))
(provide
nil-uuid
namespace-DNS
namespace-URL
namespace-OID
namespace-X500)
(provide/contract
(uuid?
(-> any/c boolean?))
(uuid-RFC-4122?
(-> uuid? boolean?))
(uuid-version
(-> uuid? exact-nonnegative-integer?))
(make-uuid-1
(-> uuid?))
(uuid-1->date
(-> uuid? date?))
(make-uuid-3
(->* (uuid? string?) (#:legacy boolean?) uuid?))
(make-uuid-4
(-> uuid?))
(make-uuid-5
(->* (uuid? string?) (#:legacy boolean?) uuid?))
(hex-string?
(-> any/c boolean?))
(hex-string->uuid
(-> hex-string? uuid?))
(uuid-string?
(-> any/c boolean?))
(string->uuid
(-> string? (or/c uuid? false/c)))
(uuid->hex-string
(-> uuid? hex-string?))
(uuid->string
(-> uuid? string?))
(uuid->urn-string
(-> uuid? string?))
(uuid=?
(-> uuid? uuid? boolean?))
(uuid<?
(-> uuid? uuid? boolean?))
(uuid>?
(-> uuid? uuid? boolean?)))