From 6ddab7e9c014a71a197193ed330dce6aa2999d49 Mon Sep 17 00:00:00 2001 From: mratsim Date: Thu, 20 Sep 2018 17:45:02 +0200 Subject: [PATCH] basic SimpleSerialize + tests --- beacon_chain/ssz.nim | 107 +++++++++++++++++++++++++++++++++++++++++++ tests/test_ssz.nim | 55 ++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 beacon_chain/ssz.nim create mode 100644 tests/test_ssz.nim diff --git a/beacon_chain/ssz.nim b/beacon_chain/ssz.nim new file mode 100644 index 000000000..3e775f133 --- /dev/null +++ b/beacon_chain/ssz.nim @@ -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.} diff --git a/tests/test_ssz.nim b/tests/test_ssz.nim new file mode 100644 index 000000000..152b35898 --- /dev/null +++ b/tests/test_ssz.nim @@ -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