mirror of
https://github.com/status-im/nim-eth.git
synced 2025-01-13 07:44:18 +00:00
2c236f6495
Currently only setting `--styleCheck:hint` as there are some dependency fixes required and the compiler seems to trip over the findnode MessageKind, findnode Message field and the findNode proc. Also over protocol.Protocol usage.
259 lines
8.0 KiB
Nim
259 lines
8.0 KiB
Nim
import
|
|
std/macros,
|
|
./object_serialization, ./priv/defs
|
|
|
|
type
|
|
RlpWriter* = object
|
|
pendingLists: seq[tuple[remainingItems, outBytes: int]]
|
|
output: seq[byte]
|
|
|
|
IntLike* = concept x, y
|
|
type T = type(x)
|
|
|
|
# arithmetic ops
|
|
x + y is T
|
|
x * y is T
|
|
x - y is T
|
|
x div y is T
|
|
x mod y is T
|
|
|
|
# some int compatibility required for big endian encoding:
|
|
x shr int is T
|
|
x shl int is T
|
|
x and 0xff is int
|
|
x < 128 is bool
|
|
|
|
Integer* = SomeInteger # or IntLike
|
|
|
|
const
|
|
wrapObjsInList* = true
|
|
|
|
proc bytesNeeded(num: Integer): int =
|
|
type IntType = type(num)
|
|
var n = num
|
|
while n != IntType(0):
|
|
inc result
|
|
n = n shr 8
|
|
|
|
proc writeBigEndian(outStream: var seq[byte], number: Integer,
|
|
lastByteIdx: int, numberOfBytes: int) =
|
|
mixin `and`, `shr`
|
|
|
|
var n = number
|
|
for i in countdown(lastByteIdx, lastByteIdx - int(numberOfBytes) + 1):
|
|
outStream[i] = byte(n and 0xff)
|
|
n = n shr 8
|
|
|
|
proc writeBigEndian(outStream: var seq[byte], number: Integer,
|
|
numberOfBytes: int) {.inline.} =
|
|
outStream.setLen(outStream.len + numberOfBytes)
|
|
outStream.writeBigEndian(number, outStream.len - 1, numberOfBytes)
|
|
|
|
proc writeCount(bytes: var seq[byte], count: int, baseMarker: byte) =
|
|
if count < THRESHOLD_LIST_LEN:
|
|
bytes.add(baseMarker + byte(count))
|
|
else:
|
|
let
|
|
origLen = bytes.len
|
|
lenPrefixBytes = count.bytesNeeded
|
|
|
|
bytes.setLen(origLen + int(lenPrefixBytes) + 1)
|
|
bytes[origLen] = baseMarker + (THRESHOLD_LIST_LEN - 1) + byte(lenPrefixBytes)
|
|
bytes.writeBigEndian(count, bytes.len - 1, lenPrefixBytes)
|
|
|
|
proc initRlpWriter*: RlpWriter =
|
|
newSeq(result.pendingLists, 0)
|
|
newSeq(result.output, 0)
|
|
|
|
proc decRet(n: var int, delta: int): int =
|
|
n -= delta
|
|
return n
|
|
|
|
proc maybeClosePendingLists(self: var RlpWriter) =
|
|
while self.pendingLists.len > 0:
|
|
let lastListIdx = self.pendingLists.len - 1
|
|
doAssert self.pendingLists[lastListIdx].remainingItems >= 1
|
|
if decRet(self.pendingLists[lastListIdx].remainingItems, 1) == 0:
|
|
# A list have been just finished. It was started in `startList`.
|
|
let listStartPos = self.pendingLists[lastListIdx].outBytes
|
|
self.pendingLists.setLen lastListIdx
|
|
|
|
# How many bytes were written since the start?
|
|
let listLen = self.output.len - listStartPos
|
|
|
|
# Compute the number of bytes required to write down the list length
|
|
let totalPrefixBytes = if listLen < int(THRESHOLD_LIST_LEN): 1
|
|
else: int(listLen.bytesNeeded) + 1
|
|
|
|
# Shift the written data to make room for the prefix length
|
|
self.output.setLen(self.output.len + totalPrefixBytes)
|
|
|
|
moveMem(addr self.output[listStartPos + totalPrefixBytes],
|
|
unsafeAddr self.output[listStartPos],
|
|
listLen)
|
|
|
|
# Write out the prefix length
|
|
if listLen < THRESHOLD_LIST_LEN:
|
|
self.output[listStartPos] = LIST_START_MARKER + byte(listLen)
|
|
else:
|
|
let listLenBytes = totalPrefixBytes - 1
|
|
self.output[listStartPos] = LEN_PREFIXED_LIST_MARKER + byte(listLenBytes)
|
|
self.output.writeBigEndian(listLen, listStartPos + listLenBytes, listLenBytes)
|
|
else:
|
|
# The currently open list is not finished yet. Nothing to do.
|
|
return
|
|
|
|
proc appendRawList(self: var RlpWriter, bytes: openArray[byte]) =
|
|
self.output.writeCount(bytes.len, LIST_START_MARKER)
|
|
self.output.add(bytes)
|
|
self.maybeClosePendingLists()
|
|
|
|
proc appendRawBytes*(self: var RlpWriter, bytes: openArray[byte]) =
|
|
self.output.add(bytes)
|
|
self.maybeClosePendingLists()
|
|
|
|
proc startList*(self: var RlpWriter, listSize: int) =
|
|
if listSize == 0:
|
|
self.appendRawList([])
|
|
else:
|
|
self.pendingLists.add((listSize, self.output.len))
|
|
|
|
proc appendBlob(self: var RlpWriter, data: openArray[byte], startMarker: byte) =
|
|
if data.len == 1 and byte(data[0]) < BLOB_START_MARKER:
|
|
self.output.add byte(data[0])
|
|
else:
|
|
self.output.writeCount(data.len, startMarker)
|
|
self.output.add data
|
|
|
|
self.maybeClosePendingLists()
|
|
|
|
proc appendImpl(self: var RlpWriter, data: string) =
|
|
appendBlob(self, data.toOpenArrayByte(0, data.high), BLOB_START_MARKER)
|
|
|
|
proc appendBlob(self: var RlpWriter, data: openArray[byte]) =
|
|
appendBlob(self, data, BLOB_START_MARKER)
|
|
|
|
proc appendBlob(self: var RlpWriter, data: openArray[char]) =
|
|
appendBlob(self, data.toOpenArrayByte(0, data.high), BLOB_START_MARKER)
|
|
|
|
proc appendInt(self: var RlpWriter, i: Integer) =
|
|
# this is created as a separate proc as an extra precaution against
|
|
# any overloading resolution problems when matching the IntLike concept.
|
|
type IntType = type(i)
|
|
|
|
if i == IntType(0):
|
|
self.output.add BLOB_START_MARKER
|
|
elif i < BLOB_START_MARKER.Integer:
|
|
self.output.add byte(i)
|
|
else:
|
|
let bytesNeeded = i.bytesNeeded
|
|
self.output.writeCount(bytesNeeded, BLOB_START_MARKER)
|
|
self.output.writeBigEndian(i, bytesNeeded)
|
|
|
|
self.maybeClosePendingLists()
|
|
|
|
proc appendFloat(self: var RlpWriter, data: float64) =
|
|
# This is not covered in the RLP spec, but Geth uses Go's
|
|
# `math.Float64bits`, which is defined here:
|
|
# https://github.com/gopherjs/gopherjs/blob/master/compiler/natives/src/math/math.go
|
|
let uintWords = cast[ptr UncheckedArray[uint32]](unsafeAddr data)
|
|
let uint64bits = (uint64(uintWords[1]) shl 32) or uint64(uintWords[0])
|
|
self.appendInt(uint64bits)
|
|
|
|
template appendImpl(self: var RlpWriter, i: Integer) =
|
|
appendInt(self, i)
|
|
|
|
template appendImpl(self: var RlpWriter, e: enum) =
|
|
appendImpl(self, int(e))
|
|
|
|
template appendImpl(self: var RlpWriter, b: bool) =
|
|
appendImpl(self, int(b))
|
|
|
|
proc appendImpl[T](self: var RlpWriter, listOrBlob: openArray[T]) =
|
|
mixin append
|
|
|
|
# TODO: This append proc should be overloaded by `openArray[byte]` after
|
|
# nim bug #7416 is fixed.
|
|
when T is (byte or char):
|
|
self.appendBlob(listOrBlob)
|
|
else:
|
|
self.startList listOrBlob.len
|
|
for i in 0 ..< listOrBlob.len:
|
|
self.append listOrBlob[i]
|
|
|
|
proc appendRecordType*(self: var RlpWriter, obj: object|tuple, wrapInList = wrapObjsInList) =
|
|
mixin enumerateRlpFields, append
|
|
|
|
if wrapInList:
|
|
self.startList(static obj.type.rlpFieldsCount)
|
|
|
|
template op(field) =
|
|
when hasCustomPragma(field, rlpCustomSerialization):
|
|
append(self, obj, field)
|
|
else:
|
|
append(self, field)
|
|
|
|
enumerateRlpFields(obj, op)
|
|
|
|
proc appendImpl(self: var RlpWriter, data: object) {.inline.} =
|
|
self.appendRecordType(data)
|
|
|
|
proc appendImpl(self: var RlpWriter, data: tuple) {.inline.} =
|
|
self.appendRecordType(data)
|
|
|
|
# We define a single `append` template with a pretty low specifity
|
|
# score in order to facilitate easier overloading with user types:
|
|
template append*[T](w: var RlpWriter; data: T) =
|
|
when data is float64:
|
|
# XXX: This works around an overloading bug.
|
|
# Apparently, integer literals will be converted to `float64`
|
|
# values with higher precedence than the generic match to Integer
|
|
appendFloat(w, data)
|
|
else:
|
|
appendImpl(w, data)
|
|
|
|
proc initRlpList*(listSize: int): RlpWriter =
|
|
result = initRlpWriter()
|
|
startList(result, listSize)
|
|
|
|
# TODO: This should return a lent value
|
|
template finish*(self: RlpWriter): seq[byte] =
|
|
doAssert self.pendingLists.len == 0, "Insufficient number of elements written to a started list"
|
|
self.output
|
|
|
|
proc encode*[T](v: T): seq[byte] =
|
|
mixin append
|
|
var writer = initRlpWriter()
|
|
writer.append(v)
|
|
return writer.finish
|
|
|
|
proc encodeInt*(i: Integer): seq[byte] =
|
|
var writer = initRlpWriter()
|
|
writer.appendInt(i)
|
|
return writer.finish
|
|
|
|
macro encodeList*(args: varargs[untyped]): seq[byte] =
|
|
var
|
|
listLen = args.len
|
|
writer = genSym(nskVar, "rlpWriter")
|
|
body = newStmtList()
|
|
append = bindSym("append", brForceOpen)
|
|
|
|
for arg in args:
|
|
body.add quote do:
|
|
`append`(`writer`, `arg`)
|
|
|
|
result = quote do:
|
|
var `writer` = initRlpList(`listLen`)
|
|
`body`
|
|
finish(`writer`)
|
|
|
|
when false:
|
|
# XXX: Currently fails with a malformed AST error on the args.len expression
|
|
template encodeList*(args: varargs[untyped]): seq[byte] =
|
|
mixin append
|
|
var writer = initRlpList(args.len)
|
|
for arg in args:
|
|
writer.append(arg)
|
|
writer.finish
|