Implement binary format, allow for conversion to int128, and allow for custom timestamps to be set

This commit is contained in:
Mythical Forest Collective 2023-09-03 03:07:31 +01:00
parent 607411d40a
commit 37420ad406
3 changed files with 76 additions and 27 deletions

View file

@ -1,7 +1,7 @@
# Package
version = "0.1.1"
author = "Mythical Forest Collective"
version = "0.2.0"
author = "Yu Vitaqua fer Chronos"
description = "An implementation of ULID!"
license = "CC0"
srcDir = "src"

View file

@ -9,6 +9,8 @@ import pkg/[
nint128
]
import pkg/nint128/vendor/stew/endians2
const HighUint80 = u128("1208925819614629174706176")
type
@ -20,13 +22,17 @@ type
lastTime: int64 = 0
random: UInt128 = u128(0)
func swapBytes(x: Int128): Int128 =
result.lo = swapBytes(cast[uint64](x.hi))
result.hi = cast[int64](swapBytes(x.lo))
proc toArray*[T](oa: openArray[T], size: static Slice[int]): array[size.len, T] =
result[0..<size.len] = oa[size]
proc randomBits(): UInt128 =
let rnd = urandom(10)
result = UInt128.fromBytesBE(
[0.byte, 0, 0, 0, 0, 0, rnd[0], rnd[1], rnd[2], rnd[3],
rnd[4], rnd[5], rnd[6], rnd[7], rnd[8], rnd[9]]
)
result = UInt128.fromBytesBE(@[0.byte, 0, 0, 0, 0, 0] & rnd)
template getTime: int64 = (epochTime() * 1000).int64
@ -37,37 +43,67 @@ proc wait(gen: NULIDGenerator): Future[int64] {.async.} =
await sleepAsync(1)
result = getTime()
proc nulid*(gen: NULIDGenerator): Future[NULID] {.async.} =
var now = getTime()
proc nulid*(gen: NULIDGenerator, timestamp: int64 = 0): Future[NULID] {.async.} =
if timestamp == 0:
var now = getTime()
if now < gen.lastTime:
raise newException(OSError, "Time went backwards!")
if now < gen.lastTime:
raise newException(OSError, "Time went backwards!")
if gen.lastTime == now:
inc gen.random
if gen.lastTime == now:
inc gen.random
if gen.random == HighUInt80:
now = await gen.wait()
if gen.random == HighUInt80:
now = await gen.wait()
else:
gen.random = randomBits()
result.timestamp = now
else:
gen.random = randomBits()
result.timestamp = now
result.timestamp = timestamp
result.randomness = gen.random
proc nulidSync*(gen: NULIDGenerator): NULID = waitFor gen.nulid()
proc nulidSync*(gen: NULIDGenerator, timestamp: int64 = 0): NULID =
result = waitFor gen.nulid(timestamp)
proc toInt128*(ulid: NULID): Int128 =
result = i128(ulid.timestamp) shl 80
result.hi += cast[int64](ulid.randomness.hi)
result.lo += ulid.randomness.lo
proc fromInt128*(_: typedesc[NULID], val: Int128): NULID =
result.timestamp = (val shr 16).hi
result.randomness = UInt128(
hi: cast[uint64]((val.hi shl 48) shr 48),
lo: val.lo
)
proc toBytes*(ulid: NULID): array[16, byte] =
when cpuEndian == littleEndian:
return cast[array[16, byte]](ulid.toInt128().swapBytes())
else:
return cast[array[16, byte]](ulid.toInt128())
proc fromBytes*(_: typedesc[NULID], ulidBytes: openArray[byte]): NULID =
if ulidBytes.len != 16:
raise newException(RangeDefect, "Given byte array must be 16 bytes long!")
when cpuEndian == littleEndian:
return NULID.fromInt128(cast[Int128](ulidBytes.toArray(0..15)).swapBytes())
else:
return NULID.fromInt128(cast[Int128](ulidBytes.toArray(0..15)))
proc parseNulid*(ulidStr: string): NULID =
if ulidStr.len != 26:
raise newException(RangeDefect, "Invalid ULID! Must be 26 characters long!")
result.timestamp = int64.decode(ulidStr[0..9])
result.randomness = UInt128.decode(ulidStr[10..25])
proc `$`*(ulid: NULID): string =
var res = i128(ulid.timestamp)
res = res shl 80
res.hi += cast[int64](ulid.randomness.hi)
res.lo += ulid.randomness.lo
result = '0' & Int128.encode(res, 26)
proc `==`*(a: NULID, b: string): bool = a == parseNulid(b)
result = '0' & Int128.encode(ulid.toInt128(), 26)

View file

@ -24,3 +24,16 @@ test "NULID Parsing":
randomness: u128("541019288874337045949482"))
check parseNulid(nulidStr) == nulid
test "NULID Int128 Conversion":
let nulid = parseNulid("01H999MBGTEA8BDS0M5AWEBB1A")
check NULID.fromInt128(nulid.toInt128()) == nulid
test "NULID Binary Format":
let
nulid = parseNulid("01H999MBGTEA8BDS0M5AWEBB1A")
nulidBytes = [1.byte, 138, 82, 154, 46, 26, 114, 144, 182, 228, 20, 42, 184, 229, 172, 42]
check nulid == NULID.fromBytes(nulidBytes)
check nulid.toBytes == nulidBytes