diff --git a/.gitignore b/.gitignore index 528610a..4532471 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +**/uuids + .vscode/.browse.* nimsuggest.log diff --git a/README.md b/README.md index fd64a5a..3b7562f 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,16 @@ API: type UUID* = object ## 128-bit UUID compliant with RFC-4122 -proc initUUID*(mostSigBits, leastSigBits: int64): UUID = - ## Initializes UUID with the specified most and least significant bits - -proc leastSigBits*(uuid: UUID): int64 {.inline.} - ## Returns 64 least significant bits of the ``uuid`` - -proc mostSigBits*(uuid: UUID): int64 {.inline.} - ## Returns 64 most significant bits of the ``uuid`` - proc `$`*(uuid: UUID): string - ## Returns a string representation of the ``uuid`` in canonical form. + ## Returns a string representation of the UUID in canonical form. proc hash*(uuid: UUID): Hash - ## Computes hash of the specified ``uuid``. + ## Computes hash of the specified UUID. proc `==`*(x, y: UUID): bool ## Returns true when the specified UUIDs are equal, false otherwise. -proc isZero*(uuid: UUID): bool - ## Returns ``true`` when the ``uuid`` is zero (not set), ``false`` otherwise. - -proc genUUID*(): UUID +proc genUUID*(): UUID = ## Returns a random (v4) UUID. ## Uses a thread-local cryptographically secure PRNG (ISAAC) seeded with ## true random values obtained from OS. diff --git a/uuids/urandom.nim b/src/urandom.nim similarity index 83% rename from uuids/urandom.nim rename to src/urandom.nim index 6078de4..25dcf9f 100644 --- a/uuids/urandom.nim +++ b/src/urandom.nim @@ -5,10 +5,8 @@ when defined(windows): type ULONG_PTR = int type HCRYPTPROV = ULONG_PTR - var PROV_RSA_FULL {.importc, header: """#include -#include """.}: DWORD - var CRYPT_VERIFYCONTEXT {.importc, header: """#include -#include """.}: DWORD + var PROV_RSA_FULL {.importc, header: "".}: DWORD + var CRYPT_VERIFYCONTEXT {.importc, header: "".}: DWORD {.push, stdcall, dynlib: "Advapi32.dll".} @@ -16,16 +14,16 @@ when defined(windows): proc CryptAcquireContext( phProv: ptr HCRYPTPROV, pszContainer: WideCString, pszProvider: WideCString, dwProvType: DWORD, dwFlags: DWORD - ): WINBOOL {.importc: "CryptAcquireContextW".} + ): WinBool {.importc: "CryptAcquireContextW".} else: proc CryptAcquireContext( phProv: ptr HCRYPTPROV, pszContainer: cstring, pszProvider: cstring, dwProvType: DWORD, dwFlags: DWORD - ): WINBOOL {.importc: "CryptAcquireContextA".} + ): WinBool {.importc: "CryptAcquireContextA".} proc CryptGenRandom( hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: pointer - ): WINBOOL {.importc: "CryptGenRandom".} + ): WinBool {.importc: "CryptGenRandom".} {.pop.} diff --git a/src/uuids.nim b/src/uuids.nim new file mode 100644 index 0000000..c4690c6 --- /dev/null +++ b/src/uuids.nim @@ -0,0 +1,87 @@ +import strutils, hashes +import isaac +import 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 = "0123456789abcdef" + var n = x + for j in countdown(len - 1, 0): + s[start + j] = HexChars[n and 0xF] + n = n shr 4 + # handle negative overflow + if n == 0 and x < 0: n = -1 + +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 + +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 + 0x8000000000004000'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 = parseHexInt(parts[0]) + mostSigBits = mostSigBits shl 16 + mostSigBits = mostSigBits or parseHexInt(parts[1]) + mostSigBits = mostSigBits shl 16 + mostSigBits = mostSigBits or parseHexInt(parts[2]) + + var leastSigBits: int64 = parseHexInt(parts[3]) + leastSigBits = leastSigBits shl 48 + leastSigBits = leastSigBits or parseHexInt(parts[4]) + + result = UUID(mostSigBits: mostSigBits, leastSigBits: leastSigBits) + +when isMainModule: + let 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) + assert(uuidStr.parseUUID() == uuid) + assert(uuidStr.parseUUID().hash() == uuid.hash()) diff --git a/uuids.nim b/uuids.nim deleted file mode 100644 index b44ede2..0000000 --- a/uuids.nim +++ /dev/null @@ -1,133 +0,0 @@ -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)) diff --git a/uuids.nimble b/uuids.nimble index ccc6e86..c470a8e 100644 --- a/uuids.nimble +++ b/uuids.nimble @@ -1,9 +1,10 @@ [Package] name: "uuids" -version: "0.1.11" +version: "0.1.1" author: "Xored Software, Inc." description: "UUID library" license: "MIT" +srcDir: "src" [Deps] -requires: "isaac >= 0.1.3" +requires: "isaac >= 0.1.0"