From 3951521a16cda008881440d0575f74e465544e5d Mon Sep 17 00:00:00 2001 From: Ruslan Mustakov Date: Thu, 15 Sep 2016 20:35:01 +0700 Subject: [PATCH] First version --- .gitignore | 8 +++++ .vscode/settings.json | 15 ++++++++ LICENSE | 20 +++++++++++ README.md | 33 +++++++++++++++++ src/urandom.nim | 65 +++++++++++++++++++++++++++++++++ src/uuids.nim | 83 +++++++++++++++++++++++++++++++++++++++++++ uuids.nimble | 7 ++++ 7 files changed, 231 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/urandom.nim create mode 100644 src/uuids.nim create mode 100644 uuids.nimble diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4532471 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +**/uuids + +.vscode/.browse.* +nimsuggest.log + +**/nimcache/** + +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..55a52c2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.exclude": { + "**/nimcache/**": true + }, + "files.trimTrailingWhitespace": true, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.rulers": [80], + "editor.renderIndentGuides": true, + "files.autoSave": "afterDelay", + "nim.licenseString": "# Copyright 2016 Xored Software, Inc.\n\n", + "nim.buildOnSave": false, + "nim.lintOnSave": true, + "nim.project": ["uuids.nim"] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d943066 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +uuids - UUID library for Nim + +The MIT License + +Copyright (c) 2016 Xored Software Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc06028 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# uuids + +UUID library for Nim. + +API: +```nim +type UUID* = object + ## 128-bit UUID compliant with RFC-4122 + +proc `$`*(uuid: UUID): string + ## Returns a string representation of the UUID in canonical form. + +proc hash*(uuid: UUID): Hash + ## Computes hash of the specified UUID. + +proc `==`*(x, y: UUID): bool + ## Returns true when the specified UUIDs are equal, false otherwise. + +proc genUUID*(): UUID = + ## Returns a random (v4) UUID. + ## Uses random values obtained from system source (e.g. urandom). + ## In the future this will use a cryptographically secure PRNG for efficiency. + +proc toUUID*(s: string): UUID {.raises: [ValueError].} + ## Converts string representation of an UUID to UUID object. + ## Raises ValueError if invalid format is provided. +``` + +## License +This library is licensed under the MIT license. +Read [LICENSE](https://github.com/pragmagic/uuids/blob/master/LICENSE) file for details. + +Copyright (c) 2016 Xored Software, Inc. diff --git a/src/urandom.nim b/src/urandom.nim new file mode 100644 index 0000000..25dcf9f --- /dev/null +++ b/src/urandom.nim @@ -0,0 +1,65 @@ +# Source: https://github.com/BlaXpirit/nim-random/blob/master/src/random/urandom.nim + +when defined(windows): + import winlean + + type ULONG_PTR = int + type HCRYPTPROV = ULONG_PTR + var PROV_RSA_FULL {.importc, header: "".}: DWORD + var CRYPT_VERIFYCONTEXT {.importc, header: "".}: DWORD + + {.push, stdcall, dynlib: "Advapi32.dll".} + + when useWinUnicode: + proc CryptAcquireContext( + phProv: ptr HCRYPTPROV, pszContainer: WideCString, + pszProvider: WideCString, dwProvType: DWORD, dwFlags: DWORD + ): WinBool {.importc: "CryptAcquireContextW".} + else: + proc CryptAcquireContext( + phProv: ptr HCRYPTPROV, pszContainer: cstring, pszProvider: cstring, + dwProvType: DWORD, dwFlags: DWORD + ): WinBool {.importc: "CryptAcquireContextA".} + + proc CryptGenRandom( + hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: pointer + ): WinBool {.importc: "CryptGenRandom".} + + {.pop.} + + var cryptProv: HCRYPTPROV = 0 + + proc urandomInit() = + let success = CryptAcquireContext( + addr cryptProv, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT + ) + if success == 0: + raise newException(OSError, "Call to CryptAcquireContext failed") + +template urandomImpl() = + when defined(windows): + if cryptProv == 0: + urandomInit() + + let success = CryptGenRandom(cryptProv, DWORD(size), addr result[0]) + if success == 0: + raise newException(OSError, "Call to CryptGenRandom failed") + + else: + var file: File + if not file.open("/dev/urandom"): + raise newException(OSError, "/dev/urandom is not available") + try: + var index = 0 + while index < size: + let bytesRead = file.readBuffer(addr result[index], size-index) + if bytesRead <= 0: + raise newException(OSError, "Can't read enough bytes from /dev/urandom") + index += bytesRead + finally: + file.close() + +proc urandom*(size: static[Natural]): array[size, uint8] = + ## Returns an ``array`` of random integers ``0 <= n < 256`` provided by + ## the operating system's cryptographic source. + urandomImpl() diff --git a/src/uuids.nim b/src/uuids.nim new file mode 100644 index 0000000..69bc6c9 --- /dev/null +++ b/src/uuids.nim @@ -0,0 +1,83 @@ +import strutils, hashes +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 + +proc genUUID*(): UUID = + ## Returns a random (v4) UUID. + ## Uses random values obtained from system source (e.g. urandom). + ## In the future this will use a cryptographically secure PRNG for efficiency. + var randBytes: array[16, uint8] = urandom(16) + copyMem(addr result.mostSigBits, addr randBytes[0], 8) + copyMem(addr result.leastSigBits, addr randBytes[8], 8) + + # 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 toUUID*(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.toUUID() == uuid) + assert(uuidStr.toUUID().hash() == uuid.hash()) diff --git a/uuids.nimble b/uuids.nimble new file mode 100644 index 0000000..538bdaa --- /dev/null +++ b/uuids.nimble @@ -0,0 +1,7 @@ +[Package] +name: "uuids" +version: "0.1.0" +author: "Xored Software, Inc." +description: "UUID library" +license: "MIT" +srcDir: "src"