basic SimpleSerialize + tests
This commit is contained in:
parent
a42a0ad851
commit
6ddab7e9c0
|
@ -0,0 +1,107 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
# SSZ Serialization (simple serialize)
|
||||
# See https://github.com/ethereum/beacon_chain/issues/100
|
||||
# and https://github.com/ethereum/beacon_chain/tree/master/ssz
|
||||
|
||||
import ./datatypes, eth_common, endians, typetraits
|
||||
|
||||
# ################### Helper functions ###################################
|
||||
func `+`[T](p: ptr T, offset: int): ptr T {.inline.}=
|
||||
## Pointer arithmetic: Addition
|
||||
const size = sizeof T
|
||||
cast[ptr T](cast[ByteAddress](p) +% offset * size)
|
||||
|
||||
func checkSize[T: not seq](x: T, pos, len: int) {.inline.}=
|
||||
# This assumes that T is packed
|
||||
doAssert pos + T.sizeof < len, "Deserialization overflow"
|
||||
|
||||
func checkSize[T](x: seq[T], pos, len: int) {.inline.}=
|
||||
# seq length is stored in an uint32 (4 bytes) for SSZ
|
||||
doAssert pos + 4 + x.len * T.sizeof < len, "Deserialization overflow"
|
||||
|
||||
template deserInt(x: var SomeInteger or byte, data: ptr byte, pos: var int) =
|
||||
when x.sizeof == 8:
|
||||
bigEndian64(x.addr, data + pos)
|
||||
inc pos, 8
|
||||
elif x.sizeof == 4:
|
||||
bigEndian32(x.addr, data + pos)
|
||||
inc pos, 4
|
||||
elif x.sizeof == 2:
|
||||
bigEndian16(x.addr, data + pos)
|
||||
inc pos, 2
|
||||
else:
|
||||
x = cast[ptr type x](data + pos)[]
|
||||
inc pos
|
||||
|
||||
func deserSeq[T](dest: var seq[T], len: int, src: ptr byte, pos: var int) =
|
||||
dest = newSeqUninitialized[T](len)
|
||||
for val in dest.mitems:
|
||||
val.deserInt(src, pos)
|
||||
|
||||
func serInt[T: SomeInteger or byte](dest: var seq[byte], src: T, buffer: var array[sizeof(T), byte]) {.inline.}=
|
||||
when T.sizeof == 8:
|
||||
bigEndian64(buffer.addr, src.unsafeAddr)
|
||||
elif T.sizeof == 4:
|
||||
bigEndian32(buffer.addr, src.unsafeAddr)
|
||||
elif T.sizeof == 2:
|
||||
bigEndian16(buffer.addr, src.unsafeAddr)
|
||||
else:
|
||||
dest.add byte(src)
|
||||
return
|
||||
dest.add buffer
|
||||
|
||||
func serInt[T: SomeInteger or byte](dest: var seq[byte], src: T) {.inline.}=
|
||||
var buffer: array[T.sizeof, byte]
|
||||
dest.serInt(src, buffer)
|
||||
|
||||
func serSeq[T: SomeInteger or byte](dest: var seq[byte], src: seq[T]) =
|
||||
dest.serInt src.len.uint32
|
||||
var buffer: array[T.sizeof, byte]
|
||||
for val in src:
|
||||
dest.serInt(val, buffer)
|
||||
|
||||
# ################### Core functions ###################################
|
||||
func deserialize(data: ptr byte, pos: var int, len: int, typ: typedesc[object]): typ =
|
||||
for field in result.fields:
|
||||
checkSize field, pos, len
|
||||
when field is EthAddress:
|
||||
copyMem(field.addr, data + pos, 20)
|
||||
inc pos, 20
|
||||
elif field is MDigest:
|
||||
const size = field.bits div 8
|
||||
copyMem(field.addr, data + pos, size)
|
||||
inc pos, size
|
||||
elif field is (SomeInteger or byte):
|
||||
field.deserInt(data, pos)
|
||||
elif field is seq[SomeInteger or byte]:
|
||||
var length: int32
|
||||
bigEndian32(length.addr, data + pos)
|
||||
inc pos, 4
|
||||
deserSeq(field, length, data, pos)
|
||||
else: # TODO: deserializing subtypes (?, depends on final spec)
|
||||
{.fatal: "Unsupported type deserialization: " & $typ.name.}
|
||||
|
||||
func deserialize*(
|
||||
data: seq[byte or uint8] or openarray[byte or uint8] or string,
|
||||
typ: typedesc[object]): typ {.inline.}=
|
||||
var pos = 0
|
||||
deserialize((ptr byte)(data[0].unsafeAddr), pos, data.len, typ)
|
||||
|
||||
func serialize*[T](value: T): seq[byte] =
|
||||
for field in value.fields:
|
||||
when field is EthAddress:
|
||||
result.add field
|
||||
elif field is MDigest:
|
||||
result.add field.data
|
||||
elif field is (SomeInteger or byte):
|
||||
result.serInt field
|
||||
elif field is seq[SomeInteger or byte]:
|
||||
result.serSeq field
|
||||
else: # TODO: Serializing subtypes (?, depends on final spec)
|
||||
{.fatal: "Unsupported type serialization: " & $typ.name.}
|
|
@ -0,0 +1,55 @@
|
|||
# beacon_chain
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
unittest, nimcrypto, eth_common, sequtils,
|
||||
../beacon_chain/ssz
|
||||
|
||||
func filled[N: static[int], T](typ: type array[N, T], value: T): array[N, T] =
|
||||
for val in result.mitems:
|
||||
val = value
|
||||
|
||||
func filled(T: type MDigest, value: byte): T =
|
||||
for val in result.data.mitems:
|
||||
val = value
|
||||
|
||||
suite "Simple serialization":
|
||||
# pending spec updates in
|
||||
# - https://github.com/ethereum/eth2.0-specs
|
||||
# - https://github.com/ethereum/beacon_chain/blob/master/tests/ssz/test_deserialize.py
|
||||
# - https://github.com/ethereum/beacon_chain/tree/master/ssz
|
||||
type
|
||||
Foo = object
|
||||
f0: uint8
|
||||
f1: uint32
|
||||
f2: EthAddress
|
||||
f3: MDigest[256]
|
||||
f4: seq[byte]
|
||||
|
||||
let expected_deser = Foo(
|
||||
f0: 5,
|
||||
f1: 0'u32 - 3,
|
||||
f2: EthAddress.filled(byte 35),
|
||||
f3: MDigest[256].filled(byte 35),
|
||||
f4: @[byte 'c'.ord, 'o'.ord, 'w'.ord]
|
||||
)
|
||||
|
||||
var expected_ser = @[
|
||||
byte 5,
|
||||
'\xFF'.ord, '\xFF'.ord, '\xFF'.ord, '\xFD'.ord,
|
||||
]
|
||||
expected_ser &= EthAddress.filled(byte 35)
|
||||
expected_ser &= MDigest[256].filled(byte 35).data
|
||||
expected_ser &= [byte 0, 0, 0, 3, 'c'.ord, 'o'.ord, 'w'.ord]
|
||||
|
||||
test "Deserialization":
|
||||
let deser = expected_ser.deserialize(Foo)
|
||||
check: expected_deser == deser
|
||||
|
||||
test "Serialization":
|
||||
let ser = expected_deser.serialize()
|
||||
check: expected_ser == ser
|
Loading…
Reference in New Issue