Generic binary codecs
This commit is contained in:
parent
e208d0b13a
commit
e1392c9126
|
@ -46,9 +46,13 @@ proc `'nb`*(n: string): NBytes = parseInt(n).NBytes
|
||||||
logutils.formatIt(NBytes): $it
|
logutils.formatIt(NBytes): $it
|
||||||
|
|
||||||
const
|
const
|
||||||
MiB = 1024.NBytes * 1024.NBytes # ByteSz, 1 mebibyte = 1,048,576 ByteSz
|
KiB = 1024.NBytes # ByteSz, 1 kibibyte = 1,024 ByteSz
|
||||||
|
MiB = KiB * 1024 # ByteSz, 1 mebibyte = 1,048,576 ByteSz
|
||||||
|
GiB = MiB * 1024 # ByteSz, 1 gibibyte = 1,073,741,824 ByteSz
|
||||||
|
|
||||||
|
proc KiBs*(v: Natural): NBytes = v.NBytes * KiB
|
||||||
proc MiBs*(v: Natural): NBytes = v.NBytes * MiB
|
proc MiBs*(v: Natural): NBytes = v.NBytes * MiB
|
||||||
|
proc GiBs*(v: Natural): NBytes = v.NBytes * GiB
|
||||||
|
|
||||||
func divUp*[T: NBytes](a, b : T): int =
|
func divUp*[T: NBytes](a, b : T): int =
|
||||||
## Division with result rounded up (rather than truncated as in 'div')
|
## Division with result rounded up (rather than truncated as in 'div')
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
import std/sugar
|
||||||
|
|
||||||
|
import pkg/libp2p
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/questionable/results
|
||||||
|
import pkg/stew/endians2
|
||||||
|
import pkg/stew/byteutils
|
||||||
|
|
||||||
|
import ../units
|
||||||
|
|
||||||
|
const MaxBufferSize = 10.MiBs.uint
|
||||||
|
|
||||||
|
proc encode*(i: uint64): seq[byte] =
|
||||||
|
@(i.toBytesBE)
|
||||||
|
|
||||||
|
proc decode*(T: type uint64, bytes: seq[byte]): ?!T =
|
||||||
|
if bytes.len >= sizeof(uint64):
|
||||||
|
success(uint64.fromBytesBE(bytes))
|
||||||
|
else:
|
||||||
|
failure("Not enough bytes to decode `uint64`")
|
||||||
|
|
||||||
|
proc encode*(i: int64): seq[byte] = cast[uint64](i).encode
|
||||||
|
proc decode*(T: type int64, bytes: seq[byte]): ?!T = uint64.decode(bytes).map((ui: uint64) => cast[int64](ui))
|
||||||
|
|
||||||
|
proc encode*(i: int): seq[byte] = cast[uint64](i).encode
|
||||||
|
proc decode*(T: type int, bytes: seq[byte]): ?!T = uint64.decode(bytes).map((ui: uint64) => cast[int](ui))
|
||||||
|
|
||||||
|
proc encode*(i: Natural): seq[byte] = cast[int](i).encode
|
||||||
|
proc decode*(T: type Natural, bytes: seq[byte]): ?!T = int.decode(bytes).map((ui: int) => cast[Natural](ui))
|
||||||
|
|
||||||
|
proc encode*(i: NBytes): seq[byte] = cast[int](i).encode
|
||||||
|
proc decode*(T: type NBytes, bytes: seq[byte]): ?!T = int.decode(bytes).map((ui: int) => cast[NBytes](ui))
|
||||||
|
|
||||||
|
proc encode*[T: enum](e: T): seq[byte] = e.ord().encode
|
||||||
|
proc decode*(T: typedesc[enum], bytes: seq[byte]): ?!T = int.decode(bytes).map((ui: int) => T(ui))
|
||||||
|
|
||||||
|
proc encode*(s: string): seq[byte] = s.toBytes
|
||||||
|
proc decode*(T: type string, bytes: seq[byte]): ?!T = success(string.fromBytes(bytes))
|
||||||
|
|
||||||
|
proc encode*(b: bool): seq[byte] = (if b: @[byte 1] else: @[byte 0])
|
||||||
|
proc decode*(T: type bool, bytes: seq[byte]): ?!T =
|
||||||
|
if bytes.len >= 1:
|
||||||
|
success(not (bytes[0] == 0.byte))
|
||||||
|
else:
|
||||||
|
failure("Not enought bytes to decode `bool`")
|
||||||
|
|
||||||
|
proc encode*[T](ts: seq[T]): seq[byte] =
|
||||||
|
if ts.len == 0:
|
||||||
|
return newSeq[byte]()
|
||||||
|
|
||||||
|
var pb = initProtoBuffer()
|
||||||
|
|
||||||
|
for t in ts:
|
||||||
|
pb.write(1, t.encode())
|
||||||
|
|
||||||
|
pb.finish
|
||||||
|
pb.buffer
|
||||||
|
|
||||||
|
|
||||||
|
proc decode*[T](_: type seq[T], bytes: seq[byte]): ?!seq[T] =
|
||||||
|
if bytes.len == 0:
|
||||||
|
return success(newSeq[T]())
|
||||||
|
|
||||||
|
var
|
||||||
|
pb = initProtoBuffer(bytes, maxSize = MaxBufferSize)
|
||||||
|
nestedBytes: seq[seq[byte]]
|
||||||
|
ts: seq[T]
|
||||||
|
|
||||||
|
if ? pb.getRepeatedField(1, nestedBytes).mapFailure:
|
||||||
|
for b in nestedBytes:
|
||||||
|
let t = ? T.decode(b)
|
||||||
|
ts.add(t)
|
||||||
|
|
||||||
|
success(ts)
|
||||||
|
|
||||||
|
proc autoencode*[T: tuple | object](tup: T): seq[byte] =
|
||||||
|
var
|
||||||
|
pb = initProtoBuffer(maxSize = MaxBufferSize)
|
||||||
|
index = 1
|
||||||
|
for f in fields(tup):
|
||||||
|
when (compiles do:
|
||||||
|
let _: seq[byte] = encode(f)):
|
||||||
|
let fBytes = encode(f)
|
||||||
|
pb.write(index, fBytes)
|
||||||
|
index.inc
|
||||||
|
else:
|
||||||
|
{.error: "provide `proc encode(a: " & $typeof(f) & "): seq[byte]` to use autoencode".}
|
||||||
|
|
||||||
|
pb.finish
|
||||||
|
pb.buffer
|
||||||
|
|
||||||
|
proc autodecode*(T: typedesc[tuple | object], bytes: seq[byte]): ?!T =
|
||||||
|
var
|
||||||
|
pb = initProtoBuffer(bytes, maxSize = MaxBufferSize)
|
||||||
|
res = default(T)
|
||||||
|
index = 1
|
||||||
|
for f in fields(res):
|
||||||
|
var buf = newSeq[byte]()
|
||||||
|
discard ? pb.getField(index, buf).mapFailure
|
||||||
|
f = ? decode(typeof(f), buf)
|
||||||
|
index.inc
|
||||||
|
|
||||||
|
success(res)
|
|
@ -4,5 +4,6 @@ import ./utils/testasyncstatemachine
|
||||||
import ./utils/testtimer
|
import ./utils/testtimer
|
||||||
import ./utils/testthen
|
import ./utils/testthen
|
||||||
import ./utils/testtrackedfutures
|
import ./utils/testtrackedfutures
|
||||||
|
import ./utils/testgenericcoders
|
||||||
|
|
||||||
{.warning[UnusedImport]: off.}
|
{.warning[UnusedImport]: off.}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import std/unittest
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/questionable/results
|
||||||
|
import pkg/codex/utils/genericcoders
|
||||||
|
import ../helpers
|
||||||
|
|
||||||
|
type
|
||||||
|
MyEnum = enum
|
||||||
|
MyEnumA = 1
|
||||||
|
MyEnumB = 2
|
||||||
|
|
||||||
|
MyObj = object
|
||||||
|
a: int
|
||||||
|
b: string
|
||||||
|
c: bool
|
||||||
|
d: MyEnum
|
||||||
|
e: seq[int]
|
||||||
|
|
||||||
|
MyTuple = (int, string, bool, MyEnum, seq[int])
|
||||||
|
|
||||||
|
proc `==`*(a, b: MyObj): bool =
|
||||||
|
(a.a == b.a) and
|
||||||
|
(a.b == b.b) and
|
||||||
|
(a.c == b.c) and
|
||||||
|
(a.d == b.d) and
|
||||||
|
(a.e == b.e)
|
||||||
|
|
||||||
|
proc `$`*(a: MyObj): string =
|
||||||
|
"a: " & $a.a &
|
||||||
|
", b: " & $a.b &
|
||||||
|
", c: " & $a.c &
|
||||||
|
", d: " & $a.d &
|
||||||
|
", e: " & $a.e
|
||||||
|
|
||||||
|
checksuite "Test encode/decode":
|
||||||
|
proc coderTest(T: type, a: T) =
|
||||||
|
let bytes = a.encode
|
||||||
|
|
||||||
|
without decoded =? T.decode(bytes), err:
|
||||||
|
fail
|
||||||
|
|
||||||
|
check:
|
||||||
|
decoded == a
|
||||||
|
|
||||||
|
test "Should encode and decode primitive values":
|
||||||
|
coderTest(int, 123)
|
||||||
|
coderTest(int, -123)
|
||||||
|
coderTest(int64, 123.int64)
|
||||||
|
coderTest(int64, -123.int64)
|
||||||
|
coderTest(Natural, 123.Natural)
|
||||||
|
coderTest(NBytes, 123.KiBs)
|
||||||
|
coderTest(string, "")
|
||||||
|
coderTest(string, "123")
|
||||||
|
coderTest(string, "abcdefghij")
|
||||||
|
coderTest(bool, false)
|
||||||
|
coderTest(bool, true)
|
||||||
|
coderTest(MyEnum, MyEnumA)
|
||||||
|
coderTest(MyEnum, MyEnumB)
|
||||||
|
coderTest(seq[int], @[1, 2, 3])
|
||||||
|
coderTest(seq[int], newSeq[int]())
|
||||||
|
|
||||||
|
checksuite "Test autoencode/autodecode":
|
||||||
|
proc autocoderTest(T: type, a: T) =
|
||||||
|
let bytes = a.autoencode
|
||||||
|
|
||||||
|
without decoded =? T.autodecode(bytes), err:
|
||||||
|
fail
|
||||||
|
|
||||||
|
check:
|
||||||
|
decoded == a
|
||||||
|
|
||||||
|
test "Should encode and decode product values":
|
||||||
|
autocoderTest(MyObj, MyObj(a: 1, b: "abc", c: true, d: MyEnumA, e: @[1, 2, 3]))
|
||||||
|
autocoderTest(MyTuple, (2, "def", false, MyEnumB, newSeq[int]()))
|
Loading…
Reference in New Issue