-
Notifications
You must be signed in to change notification settings - Fork 13
/
uuids.nim
133 lines (115 loc) · 4.74 KB
/
uuids.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import strutils, hashes
import isaac
import uuids/urandom
type
UUID* = object
## 128-bit UUID compliant with RFC-4122
mostSigBits: int64
leastSigBits: int64
template toHex(s: string, start: Natural,
x: BiggestInt, len: Positive) =
const HexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f']
var n = x
for j in countdown(len - 1, 0):
s[start + j] = HexChars[int(n and 0xF)]
n = n shr 4
# handle negative overflow
if n == 0 and x < 0: n = -1
proc uuidsParseHexInt(s: string, maxLen: int): int64 =
if s.len == 0:
raise newException(ValueError, "UUID part is empty")
if s.len > maxLen or s.len > sizeof(result) * 2:
raise newException(ValueError, "UUID part is longer than expected")
for c in s:
case c
of '0'..'9':
result = result shl 4 or (ord(c) - ord('0'))
of 'a'..'f':
result = result shl 4 or (ord(c) - ord('a') + 10)
of 'A'..'F':
result = result shl 4 or (ord(c) - ord('A') + 10)
else: raise newException(ValueError, "Invalid hex string: " & s)
proc initUUID*(mostSigBits, leastSigBits: int64): UUID =
## Initializes UUID with the specified most and least significant bits
result.mostSigBits = mostSigBits
result.leastSigBits = leastSigBits
proc leastSigBits*(uuid: UUID): int64 {.inline.} =
## Returns 64 least significant bits of the ``uuid``
uuid.leastSigBits
proc mostSigBits*(uuid: UUID): int64 {.inline.} =
## Returns 64 most significant bits of the ``uuid``
uuid.mostSigBits
proc `$`*(uuid: UUID): string =
## Returns a string representation of the ``uuid`` in canonical form.
result = newString(36)
toHex(result, 0, uuid.mostSigBits shr 32, 8)
result[8] = '-'
toHex(result, 9, uuid.mostSigBits shr 16, 4)
result[13] = '-'
toHex(result, 14, uuid.mostSigBits, 4)
result[18] = '-'
toHex(result, 19, uuid.leastSigBits shr 48, 4)
result[23] = '-'
toHex(result, 24, uuid.leastSigBits, 12)
proc hash*(uuid: UUID): Hash =
## Computes hash of the specified ``uuid``.
result = uuid.mostSigBits.hash() !& uuid.leastSigBits.hash()
result = !$result
proc `==`*(x, y: UUID): bool =
## Returns ``true`` when the specified UUIDs are equal, ``false`` otherwise.
x.mostSigBits == y.mostSigBits and x.leastSigBits == y.leastSigBits
proc isZero*(uuid: UUID): bool =
## Returns ``true`` when the ``uuid`` is zero (not set), ``false`` otherwise.
uuid.mostSigBits == 0'i64 and uuid.leastSigBits == 0'i64
var rand {.threadvar.}: IsaacGenerator
proc genUUID*(): UUID =
## Returns a random (v4) UUID.
## Uses a thread-local cryptographically secure PRNG (ISAAC) seeded with
## true random values obtained from OS.
if rand == nil:
var seed = cast[array[256, uint32]](urandom(1024))
rand = newIsaacGenerator(seed)
result.mostSigBits = cast[int64]((rand.nextU32().uint64 shl 32) or rand.nextU32())
result.leastSigBits = cast[int64]((rand.nextU32().uint64 shl 32) or rand.nextU32())
# set version to 4
result.mostSigBits = (result.mostSigBits and 0xFFFFFFFFFFFF0FFF'i64) or
0x0000000000004000'i64
# set IETF variant
result.leastSigBits = (result.leastSigBits and 0x3FFFFFFFFFFFFFFF'i64) or
0x8000000000000000'i64
proc parseUUID*(s: string): UUID {.raises: [ValueError].} =
## Converts string representation of an UUID to UUID object.
## Raises ``ValueError`` if invalid format is provided.
let parts = s.split('-')
if parts.len != 5:
raise newException(ValueError,
"UUID must consist of 5 parts separated with `-`")
var mostSigBits: int64 = uuidsParseHexInt(parts[0], 8)
mostSigBits = mostSigBits shl 16
mostSigBits = mostSigBits or uuidsParseHexInt(parts[1], 4)
mostSigBits = mostSigBits shl 16
mostSigBits = mostSigBits or uuidsParseHexInt(parts[2], 4)
var leastSigBits: int64 = uuidsParseHexInt(parts[3], 4)
leastSigBits = leastSigBits shl 48
leastSigBits = leastSigBits or uuidsParseHexInt(parts[4], 12)
result = UUID(mostSigBits: mostSigBits, leastSigBits: leastSigBits)
when isMainModule:
var uuid: UUID
assert(uuid.isZero())
for i in 1..100:
uuid = genUUID()
let uuidStr = $uuid
assert(uuidStr.len == 36)
assert(uuidStr[14] == '4') # version
assert(uuidStr[19] in {'8', '9', 'a', 'b'}) # variant (2 bits)
let parsedUUID = uuidStr.parseUUID()
assert(parsedUUID == uuid)
assert(parsedUUID.hash() == uuid.hash())
assert(mostSigBits(parsedUUID) == mostSigBits(uuid))
assert(leastSigBits(parsedUUID) == leastSigBits(uuid))
let newUUID = initUUID(mostSigBits(uuid), leastSigBits(uuid))
assert(newUUID == uuid)
assert(newUUID.hash() == uuid.hash())
assert(mostSigBits(newUUID) == mostSigBits(uuid))
assert(leastSigBits(newUUID) == leastSigBits(uuid))