nimbus-eth2/beacon_chain/ssz.nim

118 lines
4.2 KiB
Nim

# 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, options, nimcrypto
# ################### 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 eat(x: var auto, data: ptr byte, pos: var int, len: int): bool =
if pos + x.sizeof > len: return
copyMem(x.addr, data + pos, x.sizeof)
inc pos, x.sizeof
return true
func eatInt[T: SomeInteger or byte](x: var T, data: ptr byte, pos: var int, len: int):
bool =
if pos + x.sizeof > len: return
# XXX: any better way to get a suitably aligned buffer in nim???
# see also: https://github.com/nim-lang/Nim/issues/9206
var tmp: uint64
var alignedBuf = cast[ptr byte](tmp.addr)
copyMem(alignedBuf, data + pos, x.sizeof)
when x.sizeof == 8:
bigEndian64(x.addr, alignedBuf)
elif x.sizeof == 4:
bigEndian32(x.addr, alignedBuf)
elif x.sizeof == 2:
bigEndian16(x.addr, alignedBuf)
elif x.sizeof == 1:
x = cast[ptr type x](alignedBuf)[]
else:
{.fatal: "Unsupported type deserialization: " & $(type(x)).name.}
inc pos, x.sizeof
return true
func eatSeq[T: SomeInteger or byte](x: var seq[T], data: ptr byte, pos: var int,
len: int): bool =
var items: int32
if not eatInt(items, data, pos, len): return
if pos + T.sizeof * items > len: return
x = newSeqUninitialized[T](items)
for val in x.mitems:
discard eatInt(val, data, pos, len) # Bounds-checked above
return true
func serInt[T: SomeInteger or byte](dest: var seq[byte], src: T) {.inline.}=
# XXX: any better way to get a suitably aligned buffer in nim???
var tmp: T
var alignedBuf = cast[ptr array[src.sizeof, byte]](tmp.addr)
when src.sizeof == 8:
bigEndian64(alignedBuf, src.unsafeAddr)
elif src.sizeof == 4:
bigEndian32(alignedBuf, src.unsafeAddr)
elif src.sizeof == 2:
bigEndian16(alignedBuf, src.unsafeAddr)
elif src.sizeof == 1:
copyMem(alignedBuf, src.unsafeAddr, src.sizeof) # careful, aliasing..
else:
{.fatal: "Unsupported type deserialization: " & $(type(x)).name.}
dest.add alignedBuf[]
func serSeq[T: SomeInteger or byte](dest: var seq[byte], src: seq[T]) =
dest.serInt src.len.uint32
for val in src:
dest.serInt(val)
# ################### Core functions ###################################
func deserialize(data: ptr byte, pos: var int, len: int, typ: typedesc[object]):
auto =
var t: typ
for field in t.fields:
when field is EthAddress | MDigest:
if not eat(field, data, pos, len): return
elif field is (SomeInteger or byte):
if not eatInt(field, data, pos, len): return
elif field is seq[SomeInteger or byte]:
if not eatSeq(field, data, pos, len): return
else: # TODO: deserializing subtypes (?, depends on final spec)
{.fatal: "Unsupported type deserialization: " & $typ.name.}
return some(t)
func deserialize*(
data: seq[byte or uint8] or openarray[byte or uint8] or string,
typ: typedesc[object]): auto {.inline.} =
# XXX: returns Option[typ]: https://github.com/nim-lang/Nim/issues/9195
var pos = 0
return 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.}