mirror of https://github.com/status-im/nim-rlp.git
nim-rlp version 1.0
This commit is contained in:
parent
2bfc9e6270
commit
78ec433770
|
@ -1 +1,6 @@
|
|||
nimcache/
|
||||
|
||||
# ignore all executable files
|
||||
*
|
||||
!*.*
|
||||
!*/
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
language: c
|
||||
env:
|
||||
- BRANCH=devel
|
||||
# - BRANCH=master # At the moment nim-rlp supports only the devel branch of Nim
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
before_install:
|
||||
# Install nim and nimble
|
||||
- |
|
||||
if [ ! -x nim-$BRANCH/bin/nim ]; then
|
||||
git clone -b $BRANCH --depth 1 git://github.com/nim-lang/nim nim-$BRANCH/
|
||||
cd nim-$BRANCH
|
||||
sh ci/build.sh
|
||||
./koch tools -d:release
|
||||
else
|
||||
cd nim-$BRANCH
|
||||
git fetch origin
|
||||
if ! git merge FETCH_HEAD | grep "Already up-to-date"; then
|
||||
bin/nim c koch
|
||||
./koch boot -d:release
|
||||
./koch tools -d:release
|
||||
fi
|
||||
fi
|
||||
export PATH=$PWD/bin:$PATH
|
||||
cd ..
|
||||
script:
|
||||
- nimble test
|
||||
|
2
LICENSE
2
LICENSE
|
@ -186,7 +186,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2018 Status Research & Development GmbH
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
nim-rlp
|
||||
=======
|
||||
|
||||
[![Build Status](https://travis-ci.org/status-im/nim-rlp.svg?branch=master)](https://travis-ci.org/status-im/nim-rlp)
|
||||
|
||||
A Nim implementation of the Recursive Length Prefix encoding (RLP) as specified
|
||||
in the Ethereum's [Yellow Papper](https://ethereum.github.io/yellowpaper/paper.pdf)
|
||||
and [Wiki](https://github.com/ethereum/wiki/wiki/RLP).
|
||||
|
||||
## Reading RLP data
|
||||
|
||||
The `Rlp` type provided by this library represents a cursor over a RLP-encoded
|
||||
byte stream. Before instantiating such a cursor, you must convert your
|
||||
input data to a `BytesRange` object, which represents an immutable and
|
||||
thus cheap-to-copy sub-range view over an underlying `seq[byte]` instance:
|
||||
|
||||
``` nim
|
||||
proc initBytesRange*(s: var seq[byte], ibegin = 0, iend = -1): BytesRange
|
||||
proc rlpFromBytes*(data: BytesRange): Rlp
|
||||
```
|
||||
|
||||
### Streaming API
|
||||
|
||||
Once created, the `Rlp` object will offer procs such as `isList`, `isBlob`,
|
||||
`getType`, `listLen`, `blobLen` to determine the type of the value under
|
||||
the cursor. The contents of blobs can be extracted with procs such as
|
||||
`toString`, `toBytes` and `toInt` without advancing the cursor.
|
||||
|
||||
Lists can be traversed with the standard `items` iterator, which will advance
|
||||
the cursor to each sub-item position and yield the `Rlp` object at that point.
|
||||
As an alternative, `listElem` can return a new `Rlp` object adjusted to a
|
||||
particular sub-item position without advancing the original cursor.
|
||||
Keep in mind that copying `Rlp` objects is cheap and you can create as many
|
||||
cursors pointing to different positions in the RLP stream as necessary.
|
||||
|
||||
`skipElem` will advance the cursor to the next position in the current list.
|
||||
`hasData` will indicate that there are no more bytes in the stream that can
|
||||
be consumed.
|
||||
|
||||
Another way to extract data from the stream is through the universal `read`
|
||||
proc that accepts a type as a parameter. You can pass any supported type
|
||||
such as `string`, `int`, `seq[T]`, etc, including composite user-defined
|
||||
types (see [Object Serialization](#object-serialization)). The cursor
|
||||
will be advanced just past the end of the consumed object.
|
||||
|
||||
The `toXX` and `read` family of procs may raise a `BadCastError` in case
|
||||
of type mismatch with the stream contents under the cursor. A corrupted
|
||||
RLP stream or an attemp to read past the stream end will be signaled
|
||||
with the `MalformedRlpError` exception. If the RLP stream includes data
|
||||
that cannot be processed on the current platform (e.g. an integer value
|
||||
that is too large), the library will raise an `UnsupportedRlpError` exception.
|
||||
|
||||
### DOM API
|
||||
|
||||
Calling `Rlp.toNodes` at any position within the stream will return a tree
|
||||
of `RlpNode` objects representing the collection of values begging at that
|
||||
position:
|
||||
|
||||
``` nim
|
||||
type
|
||||
RlpNodeType* = enum
|
||||
rlpBlob
|
||||
rlpList
|
||||
|
||||
RlpNode* = object
|
||||
case kind*: RlpNodeType
|
||||
of rlpBlob:
|
||||
bytes*: BytesRange
|
||||
of rlpList:
|
||||
elems*: seq[RlpNode]
|
||||
```
|
||||
|
||||
As a short-cut, you can also call `decode` directly on a byte sequence to
|
||||
avoid creating a `Rlp` object when obtaining the nodes.
|
||||
For debugging purposes, you can also create a human readable representation
|
||||
of the Rlp nodes by calling the `inspect` proc:
|
||||
|
||||
``` nim
|
||||
proc inspect*(self: Rlp, indent = 0): string
|
||||
```
|
||||
|
||||
## Creating RLP data
|
||||
|
||||
The `RlpWriter` type can be used to encode RLP data. Instances are created
|
||||
with the `initRlpWriter` proc. This should be followed by one or more calls
|
||||
to `append` which is overloaded to accept arbitrary values. Finally, you can
|
||||
call `finish` to obtain the final `BytesRange`.
|
||||
|
||||
If the end result should by a RLP list of particular length, you can replace
|
||||
the initial call to `initRlpWriter` with `initRlpList(n)`. Calling `finish`
|
||||
before writing a sufficient number of elements will then result in a
|
||||
`PrematureFinalizationError`.
|
||||
|
||||
As an alternative short-cut, you can also call `encode` on an arbitrary value
|
||||
(including sequences and user-defined types) to execute all of the steps at
|
||||
once and directly obtain the final RLP bytes. `encodeList(varargs)` is another
|
||||
short-cut for creating RLP lists.
|
||||
|
||||
## Object serialization
|
||||
|
||||
As previously explained, generic procs such as `read`, `append`, `encode` and
|
||||
`decode` can be used with arbitrary used-defined object types. By default, the
|
||||
library will serialize all of the fields of the object using the `fields`
|
||||
iterator, but you can modify the order of serialization or include only a
|
||||
subset of the fields by using the `rlpFields` macro:
|
||||
|
||||
``` nim
|
||||
macro rlpFields*(T: typedesc, fields: varargs[untyped])
|
||||
|
||||
## example usage:
|
||||
|
||||
type
|
||||
Transaction = object
|
||||
amount: int
|
||||
time: DateTime
|
||||
sender: string
|
||||
receiver: string
|
||||
|
||||
rlpFields Transaction,
|
||||
sender, receiver, amount
|
||||
|
||||
...
|
||||
|
||||
var t1 = rlp.read(Transaction)
|
||||
var bytes = encode(t1)
|
||||
var t2 = bytes.decode(Transaction)
|
||||
```
|
||||
|
||||
## Contributing / Testing
|
||||
|
||||
To test the correctness of any modifications to the library, please execute
|
||||
`nimble test` at the root of the repo.
|
||||
|
||||
## License
|
||||
|
||||
This library is licensed under the Apache 2.0 license.
|
|
@ -0,0 +1,347 @@
|
|||
## This module implements RLP encoding and decoding as
|
||||
## defined in Appendix B of the Ethereum Yellow Paper:
|
||||
## https://ethereum.github.io/yellowpaper/paper.pdf
|
||||
|
||||
import
|
||||
strutils, parseutils,
|
||||
rlp/[types, writer, object_serialization],
|
||||
rlp/priv/defs
|
||||
|
||||
export
|
||||
types, writer, object_serialization
|
||||
|
||||
type
|
||||
Rlp* = object
|
||||
bytes: BytesRange
|
||||
position: int
|
||||
|
||||
RlpNodeType* = enum
|
||||
rlpBlob
|
||||
rlpList
|
||||
|
||||
RlpNode* = object
|
||||
case kind*: RlpNodeType
|
||||
of rlpBlob:
|
||||
bytes*: BytesRange
|
||||
of rlpList:
|
||||
elems*: seq[RlpNode]
|
||||
|
||||
MalformedRlpError* = object of Exception
|
||||
UnsupportedRlpError* = object of Exception
|
||||
BadCastError* = object of Exception
|
||||
|
||||
proc rlpFromBytes*(data: BytesRange): Rlp =
|
||||
result.bytes = data
|
||||
result.position = 0
|
||||
|
||||
let
|
||||
zeroBytesRlp* = rlpFromBytes(zeroBytesRange)
|
||||
|
||||
proc rlpFromHex*(input: string): Rlp =
|
||||
if input.len mod 2 != 0:
|
||||
raise newException(BadCastError,
|
||||
"The input string len should be even (assuming two characters per byte)")
|
||||
|
||||
let totalBytes = input.len div 2
|
||||
var backingStore = newSeq[byte](totalBytes)
|
||||
result.bytes = initBytesRange(backingStore)
|
||||
|
||||
for i in 0 ..< totalBytes:
|
||||
var nextByte: int
|
||||
if parseHex(input, nextByte, i*2, 2) == 2:
|
||||
result.bytes[i] = byte(nextByte)
|
||||
else:
|
||||
raise newException(BadCastError,
|
||||
"The input string contains invalid characters")
|
||||
|
||||
{.this: self.}
|
||||
|
||||
proc hasData*(self: Rlp): bool =
|
||||
position < bytes.len
|
||||
|
||||
template rawData*(self: Rlp): BytesRange =
|
||||
self.bytes
|
||||
|
||||
proc isBlob*(self: Rlp): bool =
|
||||
hasData() and bytes[position] < LIST_START_MARKER
|
||||
|
||||
proc isEmpty*(self: Rlp): bool =
|
||||
### Contains a blob or a list of zero length
|
||||
hasData() and (bytes[position] == BLOB_START_MARKER or
|
||||
bytes[position] == LIST_START_MARKER)
|
||||
|
||||
proc isList*(self: Rlp): bool =
|
||||
hasData() and bytes[position] >= LIST_START_MARKER
|
||||
|
||||
template eosError =
|
||||
raise newException(MalformedRlpError, "Read past the end of the RLP stream")
|
||||
|
||||
template requireData {.dirty.} =
|
||||
if not hasData():
|
||||
raise newException(MalformedRlpError, "Illegal operation over an empty RLP stream")
|
||||
|
||||
proc getType*(self: Rlp): RlpNodeType =
|
||||
requireData()
|
||||
return if isBlob(): rlpBlob else: rlpList
|
||||
|
||||
proc lengthBytesCount(self: Rlp): int =
|
||||
var marker = bytes[position]
|
||||
if isBlob() and marker > LEN_PREFIXED_BLOB_MARKER:
|
||||
return int(marker - LEN_PREFIXED_BLOB_MARKER)
|
||||
if isList() and marker > LEN_PREFIXED_LIST_MARKER:
|
||||
return int(marker - LEN_PREFIXED_LIST_MARKER)
|
||||
return 0
|
||||
|
||||
proc isSingleByte(self: Rlp): bool =
|
||||
hasData() and bytes[position] < BLOB_START_MARKER
|
||||
|
||||
proc payloadOffset(self: Rlp): int =
|
||||
if isSingleByte(): 0 else: 1 + lengthBytesCount()
|
||||
|
||||
template readAheadCheck(numberOfBytes) =
|
||||
if position + numberOfBytes >= bytes.len: eosError()
|
||||
|
||||
template nonCanonicalNumberError =
|
||||
raise newException(MalformedRlpError, "Small number encoded in a non-canonical way")
|
||||
|
||||
proc payloadBytesCount(self: Rlp): int =
|
||||
if not hasData():
|
||||
return 0
|
||||
|
||||
var marker = bytes[position]
|
||||
if marker < BLOB_START_MARKER:
|
||||
return 1
|
||||
if marker <= LEN_PREFIXED_BLOB_MARKER:
|
||||
result = int(marker - BLOB_START_MARKER)
|
||||
readAheadCheck(result)
|
||||
if result == 1:
|
||||
if bytes[position + 1] < BLOB_START_MARKER:
|
||||
nonCanonicalNumberError()
|
||||
return
|
||||
|
||||
template readInt(startMarker, lenPrefixMarker) =
|
||||
var
|
||||
lengthBytes = int(marker - lenPrefixMarker)
|
||||
remainingBytes = self.bytes.len - self.position
|
||||
|
||||
if remainingBytes <= lengthBytes:
|
||||
eosError()
|
||||
|
||||
if remainingBytes > 1 and self.bytes[self.position + 1] == 0:
|
||||
raise newException(MalformedRlpError, "Number encoded with a leading zero")
|
||||
|
||||
if lengthBytes > sizeof(result):
|
||||
raise newException(UnsupportedRlpError, "Message too large to fit in memory")
|
||||
|
||||
for i in 1 .. lengthBytes:
|
||||
result = (result shl 8) or int(self.bytes[self.position + i])
|
||||
|
||||
# must be greater than the short-list size list
|
||||
if result < THRESHOLD_LIST_LEN:
|
||||
nonCanonicalNumberError()
|
||||
|
||||
if marker < LIST_START_MARKER:
|
||||
readInt(BLOB_START_MARKER, LEN_PREFIXED_BLOB_MARKER)
|
||||
elif marker <= LEN_PREFIXED_LIST_MARKER:
|
||||
result = int(marker - LIST_START_MARKER)
|
||||
else:
|
||||
readInt(LIST_START_MARKER, LEN_PREFIXED_LIST_MARKER)
|
||||
|
||||
readAheadCheck(result)
|
||||
|
||||
proc blobLen*(self: Rlp): int =
|
||||
if isBlob(): payloadBytesCount() else: 0
|
||||
|
||||
proc isInt*(self: Rlp): bool =
|
||||
if not hasData():
|
||||
return false
|
||||
var marker = bytes[position]
|
||||
if marker < BLOB_START_MARKER:
|
||||
return marker != 0
|
||||
if marker == BLOB_START_MARKER:
|
||||
return true
|
||||
if marker <= LEN_PREFIXED_BLOB_MARKER:
|
||||
return bytes[position + 1] != 0
|
||||
if marker < LIST_START_MARKER:
|
||||
let offset = position + int(marker + 1 - LEN_PREFIXED_BLOB_MARKER)
|
||||
if offset >= bytes.len: eosError()
|
||||
return bytes[offset] != 0
|
||||
return false
|
||||
|
||||
template maxBytes*(o: typedesc[Ordinal]): int = sizeof(o)
|
||||
|
||||
proc toInt*(self: Rlp, IntType: typedesc): IntType =
|
||||
# XXX: self insertions are not working in generic procs
|
||||
# https://github.com/nim-lang/Nim/issues/5053
|
||||
if self.isList() or not self.hasData():
|
||||
raise newException(BadCastError, "")
|
||||
|
||||
let
|
||||
payloadStart = self.payloadOffset()
|
||||
payloadSize = self.payloadBytesCount()
|
||||
|
||||
if payloadSize > maxBytes(IntType):
|
||||
raise newException(BadCastError, "")
|
||||
|
||||
for i in payloadStart ..< (payloadStart + payloadSize):
|
||||
result = (result shl 8) or int(self.bytes[self.position + i])
|
||||
|
||||
proc toString*(self: Rlp): string =
|
||||
if not isBlob():
|
||||
raise newException(BadCastError, "")
|
||||
|
||||
let
|
||||
payloadOffset = payloadOffset()
|
||||
payloadLen = payloadBytesCount()
|
||||
remainingBytes = bytes.len - position - payloadOffset
|
||||
|
||||
if payloadLen > remainingBytes:
|
||||
eosError()
|
||||
|
||||
result = newString(payloadLen)
|
||||
for i in 0 ..< payloadLen:
|
||||
# XXX: switch to copyMem here
|
||||
result[i] = char(bytes[position + payloadOffset + i])
|
||||
|
||||
proc toBytes*(self: Rlp): BytesRange =
|
||||
if not isBlob():
|
||||
raise newException(BadCastError, "")
|
||||
|
||||
let
|
||||
payloadOffset = payloadOffset()
|
||||
payloadLen = payloadBytesCount()
|
||||
ibegin = position + payloadOffset
|
||||
iend = ibegin + payloadLen
|
||||
|
||||
result = bytes.slice(ibegin, iend)
|
||||
|
||||
proc currentElemEnd(self: Rlp): int =
|
||||
result = position
|
||||
|
||||
if not hasData():
|
||||
return
|
||||
|
||||
if isSingleByte():
|
||||
result += 1
|
||||
elif isBlob() or isList():
|
||||
result += payloadOffset() + payloadBytesCount()
|
||||
|
||||
proc skipElem*(rlp: var Rlp) =
|
||||
rlp.position = rlp.currentElemEnd
|
||||
|
||||
iterator items*(self: var Rlp): var Rlp =
|
||||
assert isList()
|
||||
|
||||
var
|
||||
payloadOffset = payloadOffset()
|
||||
payloadEnd = position + payloadOffset + payloadBytesCount()
|
||||
|
||||
if payloadEnd > bytes.iend:
|
||||
raise newException(MalformedRlpError, "List length extends past the end of the stream")
|
||||
|
||||
position += payloadOffset
|
||||
|
||||
while position < payloadEnd:
|
||||
let elemEnd = currentElemEnd()
|
||||
yield self
|
||||
position = elemEnd
|
||||
|
||||
proc listElem*(self: Rlp, i: int): Rlp =
|
||||
let payload = bytes.slice payloadOffset()
|
||||
result = rlpFromBytes payload
|
||||
var pos = 0
|
||||
while pos < i and result.hasData:
|
||||
result.position = result.currentElemEnd()
|
||||
inc pos
|
||||
|
||||
proc listLen*(self: Rlp): int =
|
||||
if not isList():
|
||||
return 0
|
||||
|
||||
var rlp = self
|
||||
for elem in rlp:
|
||||
inc result
|
||||
|
||||
proc read*(rlp: var Rlp, T: type string): string =
|
||||
result = rlp.toString
|
||||
rlp.skipElem
|
||||
|
||||
proc read*(rlp: var Rlp, T: type int): int =
|
||||
result = rlp.toInt(int)
|
||||
rlp.skipElem
|
||||
|
||||
proc read*[E](rlp: var Rlp, T: typedesc[seq[E]]): T =
|
||||
if not rlp.isList:
|
||||
raise newException(BadCastError, "The source RLP is not a list.")
|
||||
|
||||
result = newSeqOfCap[E](rlp.listLen)
|
||||
|
||||
for elem in rlp:
|
||||
result.add rlp.read(E)
|
||||
|
||||
proc read*(rlp: var Rlp, T: typedesc[object|tuple]): T =
|
||||
mixin enumerateRlpFields, read
|
||||
|
||||
template op(field) =
|
||||
field = rlp.read(type(field))
|
||||
|
||||
enumerateRlpFields(result, op)
|
||||
|
||||
proc toNodes*(self: var Rlp): RlpNode =
|
||||
requireData()
|
||||
|
||||
if isList():
|
||||
result.kind = rlpList
|
||||
newSeq result.elems, 0
|
||||
for e in self:
|
||||
result.elems.add e.toNodes
|
||||
else:
|
||||
assert isBlob()
|
||||
result.kind = rlpBlob
|
||||
result.bytes = toBytes()
|
||||
position = currentElemEnd()
|
||||
|
||||
proc decode*(bytes: openarray[byte]): RlpNode =
|
||||
var
|
||||
bytesCopy = @bytes
|
||||
rlp = rlpFromBytes initBytesRange(bytesCopy)
|
||||
return rlp.toNodes
|
||||
|
||||
template decode*(bytes: BytesRange, T: typedesc): untyped =
|
||||
var rlp = rlpFromBytes bytes
|
||||
rlp.read(T)
|
||||
|
||||
template decode*(bytes: openarray[byte], T: typedesc): T =
|
||||
var bytesCopy = @bytes
|
||||
decode(initBytesRange(bytesCopy), T)
|
||||
|
||||
proc inspectAux(self: var Rlp, depth: int, output: var string) =
|
||||
if not hasData():
|
||||
return
|
||||
|
||||
template indent =
|
||||
for i in 0..<depth:
|
||||
output.add " "
|
||||
|
||||
indent()
|
||||
|
||||
if self.isSingleByte:
|
||||
output.add "byte "
|
||||
output.add $bytes[position]
|
||||
elif self.isBlob:
|
||||
output.add '"'
|
||||
output.add self.toString
|
||||
output.add '"'
|
||||
else:
|
||||
output.add "{\n"
|
||||
for subitem in self:
|
||||
inspectAux(subitem, depth + 1, output)
|
||||
output.add "\n"
|
||||
indent()
|
||||
output.add "}"
|
||||
|
||||
proc inspect*(self: Rlp, indent = 0): string =
|
||||
var rlpCopy = self
|
||||
result = newStringOfCap(bytes.len)
|
||||
inspectAux(rlpCopy, indent, result)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
mode = ScriptMode.Verbose
|
||||
|
||||
packageName = "rlp"
|
||||
version = "1.0.0"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "RLP serialization library for Nim"
|
||||
license = "Apache2"
|
||||
skipDirs = @["tests"]
|
||||
|
||||
requires "nim >= 0.17.0"
|
||||
|
||||
proc configForTests() =
|
||||
--hints: off
|
||||
--debuginfo
|
||||
--path: "."
|
||||
--run
|
||||
|
||||
task test, "run CPU tests":
|
||||
configForTests()
|
||||
setCommand "c", "tests/all.nim"
|
|
@ -0,0 +1,18 @@
|
|||
import macros
|
||||
|
||||
template enumerateRlpFields*[T](x: T, op: untyped) =
|
||||
for f in fields(x): op(f)
|
||||
|
||||
macro rlpFields*(T: typedesc, fields: varargs[untyped]): untyped =
|
||||
var body = newStmtList()
|
||||
let
|
||||
ins = genSym(nskParam, "instance")
|
||||
op = genSym(nskParam, "op")
|
||||
|
||||
for field in fields:
|
||||
body.add quote do: `op`(`ins`.`field`)
|
||||
|
||||
result = quote do:
|
||||
template enumerateRlpFields*(`ins`: `T`, `op`: untyped) {.inject.} =
|
||||
`body`
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import
|
||||
../types
|
||||
|
||||
const
|
||||
MAX_LENGTH_BYTES* = 8
|
||||
|
||||
BLOB_START_MARKER* = byte(128)
|
||||
LIST_START_MARKER* = byte(192)
|
||||
|
||||
THRESHOLD_LIST_LEN* = 56
|
||||
|
||||
LEN_PREFIXED_BLOB_MARKER* = byte(BLOB_START_MARKER + THRESHOLD_LIST_LEN - 1) # 183
|
||||
LEN_PREFIXED_LIST_MARKER* = byte(LIST_START_MARKER + THRESHOLD_LIST_LEN - 1) # 247
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
proc baseAddr*[T](x: openarray[T]): pointer = cast[pointer](x)
|
||||
|
||||
proc shift*(p: pointer, delta: int): pointer =
|
||||
cast[pointer](cast[int](p) + delta)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
type
|
||||
Bytes* = seq[byte]
|
||||
|
||||
BytesRange* = object
|
||||
bytes*: Bytes
|
||||
ibegin*, iend*: int
|
||||
|
||||
proc initBytesRange*(s: var Bytes, ibegin = 0, iend = -1): BytesRange =
|
||||
let e = if iend < 0: s.len + iend + 1
|
||||
else: iend
|
||||
assert ibegin >= 0 and e <= s.len
|
||||
|
||||
shallow(s)
|
||||
result.bytes = s
|
||||
result.ibegin = ibegin
|
||||
result.iend = e
|
||||
|
||||
var
|
||||
zeroBytes*: Bytes = @[]
|
||||
zeroBytesRange* = initBytesRange(zeroBytes)
|
||||
|
||||
proc `[]`*(r: BytesRange, i: int): byte =
|
||||
r.bytes[r.ibegin + i]
|
||||
|
||||
proc `[]`*(r: var BytesRange, i: int): var byte =
|
||||
r.bytes[r.ibegin + i]
|
||||
|
||||
# XXX: change this to a template after fixing
|
||||
# https://github.com/nim-lang/Nim/issues/7097
|
||||
proc `[]=`*(r: var BytesRange, i: int, v: byte) =
|
||||
r.bytes[r.ibegin + i] = v
|
||||
|
||||
template len*(r: BytesRange): int =
|
||||
r.iend - r.ibegin
|
||||
|
||||
proc slice*(r: BytesRange, ibegin: int, iend = -1): BytesRange =
|
||||
result.bytes = r.bytes
|
||||
result.ibegin = r.ibegin + ibegin
|
||||
let e = if iend < 0: r.iend + iend + 1
|
||||
else: r.ibegin + r.iend
|
||||
assert ibegin >= 0 and e <= result.bytes.len
|
||||
result.iend = e
|
||||
|
||||
iterator items*(r: BytesRange): byte =
|
||||
for i in r.ibegin ..< r.iend:
|
||||
yield r.bytes[i]
|
||||
|
||||
converter fromSeq*(s: Bytes): BytesRange =
|
||||
var seqCopy = s
|
||||
return initBytesRange(seqCopy)
|
||||
|
||||
converter fromVarSeq*(s: var Bytes): BytesRange =
|
||||
return initBytesRange(s)
|
||||
|
||||
when false:
|
||||
import
|
||||
ptr_arith, keccak_tiny
|
||||
|
||||
type
|
||||
KeccakHash* = Hash[256]
|
||||
|
||||
proc toInputRange*(r: BytesRange): keccak_tiny.InputRange =
|
||||
result[0] = r.bytes.seqBaseAddr.shift(r.ibegin)
|
||||
result[1] = r.len
|
||||
|
||||
proc keccak*(r: BytesRange): KeccakHash = keccak_256(r)
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
import
|
||||
types, ptr_arith, object_serialization, priv/defs, macros
|
||||
|
||||
type
|
||||
RlpWriter* = object
|
||||
pendingLists: seq[tuple[remainingItems, outBytes: int]]
|
||||
output: Bytes
|
||||
|
||||
PrematureFinalizationError* = object of Exception
|
||||
|
||||
proc bytesNeeded(num: int): int =
|
||||
var n = num
|
||||
while n != 0:
|
||||
inc result
|
||||
n = n shr 8
|
||||
|
||||
proc writeBigEndian(outStream: var Bytes, number: int,
|
||||
lastByteIdx: int, numberOfBytes: int) {.inline.} =
|
||||
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 Bytes, number: int,
|
||||
numberOfBytes: int) {.inline.} =
|
||||
outStream.setLen(outStream.len + numberOfBytes)
|
||||
outStream.writeBigEndian(number, outStream.len - 1, numberOfBytes)
|
||||
|
||||
proc writeCount(bytes: var Bytes, 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 add(outStream: var Bytes, newChunk: BytesRange) =
|
||||
let prevLen = outStream.len
|
||||
outStream.setLen(prevLen + newChunk.len)
|
||||
# XXX: Use copyMem here
|
||||
for i in 0 ..< newChunk.len:
|
||||
outStream[prevLen + i] = newChunk[i]
|
||||
|
||||
{.this: self.}
|
||||
{.experimental.}
|
||||
|
||||
using
|
||||
self: var RlpWriter
|
||||
|
||||
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) =
|
||||
while pendingLists.len > 0:
|
||||
let lastListIdx = pendingLists.len - 1
|
||||
assert pendingLists[lastListIdx].remainingItems >= 1
|
||||
if decRet(pendingLists[lastListIdx].remainingItems, 1) == 0:
|
||||
# A list have been just finished. It was started in `startList`.
|
||||
let listStartPos = pendingLists[lastListIdx].outBytes
|
||||
pendingLists.setLen lastListIdx
|
||||
|
||||
# How many bytes were written since the start?
|
||||
let listLen = 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
|
||||
output.setLen(output.len + totalPrefixBytes)
|
||||
let outputBaseAddr = output.baseAddr
|
||||
|
||||
moveMem(outputBaseAddr.shift(listStartPos + totalPrefixBytes),
|
||||
outputBaseAddr.shift(listStartPos),
|
||||
listLen)
|
||||
|
||||
# Write out the prefix length
|
||||
if listLen < THRESHOLD_LIST_LEN:
|
||||
output[listStartPos] = LIST_START_MARKER + byte(listLen)
|
||||
else:
|
||||
let listLenBytes = totalPrefixBytes - 1
|
||||
output[listStartPos] = LEN_PREFIXED_LIST_MARKER + byte(listLenBytes)
|
||||
output.writeBigEndian(listLen, listStartPos + listLenBytes, listLenBytes)
|
||||
else:
|
||||
# The currently open list is not finished yet. Nothing to do.
|
||||
return
|
||||
|
||||
proc appendRawList(self; bytes: BytesRange) =
|
||||
output.writeCount(bytes.len, LIST_START_MARKER)
|
||||
output.add(bytes)
|
||||
maybeClosePendingLists()
|
||||
|
||||
proc startList(self; listSize: int) =
|
||||
if listSize == 0:
|
||||
appendRawList zeroBytesRange
|
||||
else:
|
||||
pendingLists.add((listSize, output.len))
|
||||
|
||||
template appendImpl(self; data, startMarker) =
|
||||
if data.len == 1 and byte(data[0]) < BLOB_START_MARKER:
|
||||
output.add byte(data[0])
|
||||
else:
|
||||
output.writeCount(data.len, startMarker)
|
||||
|
||||
let startPos = output.len
|
||||
output.setLen(startPos + data.len)
|
||||
copyMem(output.baseAddr.shift(startPos), data.baseAddr, data.len)
|
||||
|
||||
maybeClosePendingLists()
|
||||
|
||||
proc append*(self; data: string) =
|
||||
appendImpl(self, data, BLOB_START_MARKER)
|
||||
|
||||
proc append*(self; i: int) =
|
||||
if i == 0:
|
||||
output.add BLOB_START_MARKER
|
||||
elif i < int(BLOB_START_MARKER):
|
||||
output.add byte(i)
|
||||
else:
|
||||
let bytesNeeded = i.bytesNeeded
|
||||
output.writeCount(bytesNeeded, BLOB_START_MARKER)
|
||||
output.writeBigEndian(i, bytesNeeded)
|
||||
|
||||
maybeClosePendingLists()
|
||||
|
||||
proc append*[T](self; list: openarray[T]) =
|
||||
self.startList list.len
|
||||
for i in 0 ..< list.len:
|
||||
self.append list[i]
|
||||
|
||||
proc append*(self; data: object|tuple) =
|
||||
mixin enumerateRlpFields, append
|
||||
template op(x) = append(self, x)
|
||||
enumerateRlpFields(data, op)
|
||||
|
||||
proc initRlpList*(listSize: int): RlpWriter =
|
||||
result = initRlpWriter()
|
||||
startList(result, listSize)
|
||||
|
||||
proc finish*(self): BytesRange =
|
||||
if pendingLists.len > 0:
|
||||
raise newException(PrematureFinalizationError,
|
||||
"Insufficient number of elements written to a started list")
|
||||
result = initBytesRange(output)
|
||||
|
||||
proc encode*[T](v: T): BytesRange =
|
||||
var writer = initRlpWriter()
|
||||
writer.append(v)
|
||||
return writer.finish
|
||||
|
||||
macro encodeList*(args: varargs[untyped]): BytesRange =
|
||||
var
|
||||
listLen = args.len
|
||||
writer = genSym(nskVar, "rlpWriter")
|
||||
body = newStmtList()
|
||||
|
||||
for arg in args:
|
||||
body.add quote do:
|
||||
`writer`.append(`arg`)
|
||||
|
||||
result = quote do:
|
||||
var `writer` = initRlpList(`listLen`)
|
||||
`body`
|
||||
`writer`.finish
|
||||
|
||||
when false:
|
||||
# XXX: Currently fails with a malformed AST error on the args.len expression
|
||||
template encodeList*(args: varargs[untyped]): BytesRange =
|
||||
var writer = initRlpList(args.len)
|
||||
for arg in args:
|
||||
writer.append(arg)
|
||||
writer.finish
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import
|
||||
test_api_usage, test_json_suite, test_object_serialization
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"listsoflists2": {
|
||||
"in": "VALID",
|
||||
"out": "c7c0c1c0c3c0c1c0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"int32Overflow": {
|
||||
"in": "INVALID",
|
||||
"out": "bf0f000000000000021111"
|
||||
},
|
||||
|
||||
"int32Overflow2": {
|
||||
"in": "INVALID",
|
||||
"out": "ff0f000000000000021111"
|
||||
},
|
||||
|
||||
"wrongSizeList": {
|
||||
"in": "INVALID",
|
||||
"out": "f80180"
|
||||
},
|
||||
|
||||
"wrongSizeList2": {
|
||||
"in": "INVALID",
|
||||
"out": "f80100"
|
||||
},
|
||||
|
||||
"incorrectLengthInArray": {
|
||||
"in": "INVALID",
|
||||
"out": "b9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0"
|
||||
},
|
||||
|
||||
"randomRLP": {
|
||||
"in": "INVALID",
|
||||
"out": "f861f83eb9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df027b90015002d5ef8325ae4d034df55d4b58d0dfba64d61ddd17be00000b9001a00dae30907045a2f66fa36f2bb8aa9029cbb0b8a7b3b5c435ab331"
|
||||
},
|
||||
|
||||
"bytesShouldBeSingleByte00": {
|
||||
"in": "INVALID",
|
||||
"out": "8100"
|
||||
},
|
||||
|
||||
"bytesShouldBeSingleByte01": {
|
||||
"in": "INVALID",
|
||||
"out": "8100"
|
||||
},
|
||||
|
||||
"bytesShouldBeSingleByte7F": {
|
||||
"in": "INVALID",
|
||||
"out": "817F"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"T1": {
|
||||
"in": "INVALID",
|
||||
"out": ""
|
||||
},
|
||||
|
||||
"T2": {
|
||||
"in": "INVALID",
|
||||
"out": "00ab"
|
||||
},
|
||||
|
||||
"T3": {
|
||||
"in": "INVALID",
|
||||
"out": "0000ff"
|
||||
},
|
||||
|
||||
"T4": {
|
||||
"in": "VALID",
|
||||
"out": "83646F67636174"
|
||||
},
|
||||
|
||||
"T5": {
|
||||
"in": "INVALID",
|
||||
"out": "83646F"
|
||||
},
|
||||
|
||||
"T6": {
|
||||
"in": "INVALID",
|
||||
"out": "c7c0c1c0c3c0c1c0ff"
|
||||
},
|
||||
|
||||
"T7": {
|
||||
"in": "INVALID",
|
||||
"out": "c7c0c1c0c3c0c1"
|
||||
},
|
||||
|
||||
"T8": {
|
||||
"in": "INVALID",
|
||||
"out": "8102"
|
||||
},
|
||||
|
||||
"T9": {
|
||||
"in": "INVALID",
|
||||
"out": "b800"
|
||||
},
|
||||
|
||||
"T10": {
|
||||
"in": "INVALID",
|
||||
"out": "b800"
|
||||
},
|
||||
|
||||
"T11": {
|
||||
"in": "INVALID",
|
||||
"out": "b90000"
|
||||
},
|
||||
|
||||
"T12": {
|
||||
"in": "INVALID",
|
||||
"out": "ba0002ffff"
|
||||
},
|
||||
|
||||
"T13": {
|
||||
"in": "INVALID",
|
||||
"out": "8154"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
{
|
||||
"emptystring": {
|
||||
"in": "",
|
||||
"out": "80"
|
||||
},
|
||||
"bytestring00": {
|
||||
"in": "\u0000",
|
||||
"out": "00"
|
||||
},
|
||||
"bytestring01": {
|
||||
"in": "\u0001",
|
||||
"out": "01"
|
||||
},
|
||||
"bytestring7F": {
|
||||
"in": "\u007F",
|
||||
"out": "7f"
|
||||
},
|
||||
"shortstring": {
|
||||
"in": "dog",
|
||||
"out": "83646f67"
|
||||
},
|
||||
"shortstring2": {
|
||||
"in": "Lorem ipsum dolor sit amet, consectetur adipisicing eli",
|
||||
"out": "b74c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69"
|
||||
},
|
||||
"longstring": {
|
||||
"in": "Lorem ipsum dolor sit amet, consectetur adipisicing elit",
|
||||
"out": "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974"
|
||||
},
|
||||
"longstring2": {
|
||||
"in": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat",
|
||||
"out": "b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174"
|
||||
},
|
||||
"zero": {
|
||||
"in": 0,
|
||||
"out": "80"
|
||||
},
|
||||
"smallint": {
|
||||
"in": 1,
|
||||
"out": "01"
|
||||
},
|
||||
"smallint2": {
|
||||
"in": 16,
|
||||
"out": "10"
|
||||
},
|
||||
"smallint3": {
|
||||
"in": 79,
|
||||
"out": "4f"
|
||||
},
|
||||
"smallint4": {
|
||||
"in": 127,
|
||||
"out": "7f"
|
||||
},
|
||||
"mediumint1": {
|
||||
"in": 128,
|
||||
"out": "8180"
|
||||
},
|
||||
"mediumint2": {
|
||||
"in": 1000,
|
||||
"out": "8203e8"
|
||||
},
|
||||
"mediumint3": {
|
||||
"in": 100000,
|
||||
"out": "830186a0"
|
||||
},
|
||||
"mediumint4": {
|
||||
"in": "#83729609699884896815286331701780722",
|
||||
"out": "8f102030405060708090a0b0c0d0e0f2"
|
||||
},
|
||||
"mediumint5": {
|
||||
"in": "#105315505618206987246253880190783558935785933862974822347068935681",
|
||||
"out": "9c0100020003000400050006000700080009000a000b000c000d000e01"
|
||||
},
|
||||
"emptylist": {
|
||||
"in": [],
|
||||
"out": "c0"
|
||||
},
|
||||
"stringlist": {
|
||||
"in": [ "dog", "god", "cat" ],
|
||||
"out": "cc83646f6783676f6483636174"
|
||||
},
|
||||
"multilist": {
|
||||
"in": [ "zw", [ 4 ], 1 ],
|
||||
"out": "c6827a77c10401"
|
||||
},
|
||||
"shortListMax1": {
|
||||
"in": [ "asdf", "qwer", "zxcv", "asdf","qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"],
|
||||
"out": "f784617364668471776572847a78637684617364668471776572847a78637684617364668471776572847a78637684617364668471776572"
|
||||
},
|
||||
"longList1" : {
|
||||
"in" : [
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"]
|
||||
],
|
||||
"out": "f840cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376"
|
||||
},
|
||||
"longList2" : {
|
||||
"in" : [
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"],
|
||||
["asdf","qwer","zxcv"]
|
||||
],
|
||||
"out": "f90200cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376"
|
||||
},
|
||||
|
||||
"listsoflists": {
|
||||
"in": [ [ [], [] ], [] ],
|
||||
"out": "c4c2c0c0c0"
|
||||
},
|
||||
"listsoflists2": {
|
||||
"in": [ [], [[]], [ [], [[]] ] ],
|
||||
"out": "c7c0c1c0c3c0c1c0"
|
||||
},
|
||||
"dictTest1" : {
|
||||
"in" : [
|
||||
["key1", "val1"],
|
||||
["key2", "val2"],
|
||||
["key3", "val3"],
|
||||
["key4", "val4"]
|
||||
],
|
||||
"out" : "ecca846b6579318476616c31ca846b6579328476616c32ca846b6579338476616c33ca846b6579348476616c34"
|
||||
},
|
||||
"bigint": {
|
||||
"in": "#115792089237316195423570985008687907853269984665640564039457584007913129639936",
|
||||
"out": "a1010000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import
|
||||
unittest, strutils,
|
||||
rlp, util/json_testing
|
||||
|
||||
proc q(s: string): string = "\"" & s & "\""
|
||||
proc i(s: string): string = s.replace(" ").replace("\n")
|
||||
proc inspectMatch(r: Rlp, s: string): bool = r.inspect.i == s.i
|
||||
|
||||
test "empty bytes are not a proper RLP":
|
||||
var rlp = rlpFromBytes Bytes(@[])
|
||||
|
||||
check:
|
||||
not rlp.hasData
|
||||
not rlp.isBlob
|
||||
not rlp.isList
|
||||
not rlp.isEmpty
|
||||
|
||||
expect Exception:
|
||||
discard rlp.getType
|
||||
|
||||
expect Exception:
|
||||
for e in rlp:
|
||||
discard e.getType
|
||||
|
||||
test "you cannot finish a list without appending enough elements":
|
||||
var writer = initRlpList(3)
|
||||
writer.append "foo"
|
||||
writer.append "bar"
|
||||
|
||||
expect PrematureFinalizationError:
|
||||
let result = writer.finish
|
||||
|
||||
proc withNewLines(x: string): string = x & "\n"
|
||||
|
||||
test "encode and decode lists":
|
||||
var writer = initRlpList(3)
|
||||
writer.append "foo"
|
||||
writer.append ["bar", "baz"]
|
||||
writer.append [30, 40, 50]
|
||||
|
||||
var
|
||||
bytes = writer.finish
|
||||
rlp = rlpFromBytes bytes
|
||||
|
||||
check:
|
||||
bytes.hexRepr == "d183666f6fc8836261728362617ac31e2832"
|
||||
rlp.inspectMatch """
|
||||
{
|
||||
"foo"
|
||||
{
|
||||
"bar"
|
||||
"baz"
|
||||
}
|
||||
{
|
||||
byte 30
|
||||
byte 40
|
||||
byte 50
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
bytes = encodeList(6000,
|
||||
"Lorem ipsum dolor sit amet",
|
||||
"Donec ligula tortor, egestas eu est vitae")
|
||||
|
||||
rlp = rlpFromBytes bytes
|
||||
check:
|
||||
rlp.listLen == 3
|
||||
rlp.listElem(0).toInt(int) == 6000
|
||||
rlp.listElem(1).toString == "Lorem ipsum dolor sit amet"
|
||||
rlp.listElem(2).toString == "Donec ligula tortor, egestas eu est vitae"
|
||||
|
||||
test "encoding length":
|
||||
let listBytes = encode([1,2,3,4,5])
|
||||
let listRlp = rlpFromBytes listBytes
|
||||
check listRlp.listLen == 5
|
||||
|
||||
let emptyListBytes = encode ""
|
||||
check emptyListBytes.len == 1
|
||||
let emptyListRlp = rlpFromBytes emptyListBytes
|
||||
check emptyListRlp.blobLen == 0
|
||||
|
||||
test "basic decoding":
|
||||
var rlp = rlpFromHex("856d6f6f7365")
|
||||
check rlp.inspect == q"moose"
|
||||
|
||||
test "malformed/truncated RLP":
|
||||
var rlp = rlpFromHex("b8056d6f6f7365")
|
||||
expect MalformedRlpError:
|
||||
discard rlp.inspect
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import
|
||||
os, strutils,
|
||||
util/json_testing
|
||||
|
||||
for file in walkDirRec("tests/cases"):
|
||||
if file.endsWith("json"):
|
||||
runTests(file)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import
|
||||
unittest, times, rlp, util/json_testing
|
||||
|
||||
type
|
||||
Transaction = object
|
||||
amount: int
|
||||
time: DateTime
|
||||
sender: string
|
||||
receiver: string
|
||||
|
||||
Foo = object
|
||||
x: int
|
||||
y: string
|
||||
z: seq[int]
|
||||
|
||||
Bar = object
|
||||
b: string
|
||||
f: Foo
|
||||
|
||||
rlpFields Foo,
|
||||
x, y, z
|
||||
|
||||
rlpFields Transaction,
|
||||
sender, receiver, amount
|
||||
|
||||
proc default(T: typedesc): T = discard
|
||||
|
||||
test "encoding and decoding an object":
|
||||
var originalBar = Bar(b: "abracadabra",
|
||||
f: Foo(x: 5, y: "hocus pocus", z: @[100, 200, 300]))
|
||||
|
||||
var bytes = encode(originalBar)
|
||||
|
||||
var r = rlpFromBytes(bytes)
|
||||
var restoredBar = r.read(Bar)
|
||||
|
||||
check:
|
||||
originalBar == restoredBar
|
||||
|
||||
var t1 = Transaction(time: now(), amount: 1000, sender: "Alice", receiver: "Bob")
|
||||
bytes = encode(t1)
|
||||
var t2 = bytes.decode(Transaction)
|
||||
|
||||
check:
|
||||
bytes.hexRepr == "85416c69636583426f628203e8" # verifies that Alice comes first
|
||||
t2.time == default(DateTime)
|
||||
t2.sender == "Alice"
|
||||
t2.receiver == "Bob"
|
||||
t2.amount == 1000
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import
|
||||
json, strutils, rlp
|
||||
|
||||
proc append(output: var RlpWriter, js: JsonNode) =
|
||||
case js.kind
|
||||
of JNull, JFloat, JObject:
|
||||
raise newException(ValueError, "Unsupported JSON value type " & $js.kind)
|
||||
of JBool:
|
||||
output.append js.bval.int
|
||||
of JInt:
|
||||
output.append int(js.num)
|
||||
of JString:
|
||||
output.append js.str
|
||||
of JArray:
|
||||
output.append js.elems
|
||||
|
||||
proc hexRepr*(bytes: BytesRange): string =
|
||||
result = newStringOfCap(bytes.len * 2)
|
||||
for byte in bytes:
|
||||
result.add(toHex(int(byte), 2).toLowerAscii)
|
||||
|
||||
proc `==`(lhs: JsonNode, rhs: string): bool =
|
||||
lhs.kind == JString and lhs.str == rhs
|
||||
|
||||
proc runTests*(filename: string) =
|
||||
let js = json.parseFile(filename)
|
||||
|
||||
for testname, testdata in js:
|
||||
template testStatus(status: string) =
|
||||
echo status, " ", filename, " :: ", testname
|
||||
|
||||
let
|
||||
input = testdata{"in"}
|
||||
output = testdata{"out"}
|
||||
|
||||
if input.isNil or output.isNil or output.kind != JString:
|
||||
testStatus "IGNORED"
|
||||
continue
|
||||
|
||||
if input == "VALID":
|
||||
var rlp = rlpFromHex(output.str)
|
||||
discard rlp.inspect
|
||||
elif input == "INVALID":
|
||||
var success = false
|
||||
var inspectOutput = ""
|
||||
try:
|
||||
var rlp = rlpFromHex(output.str)
|
||||
inspectOutput = rlp.inspect(1)
|
||||
discard rlp.getType
|
||||
while rlp.hasData: discard rlp.toNodes
|
||||
except MalformedRlpError, ValueError:
|
||||
success = true
|
||||
if not success:
|
||||
testStatus "FAILED"
|
||||
echo " ACCEPTED MALFORMED BYTES: ", output.str
|
||||
echo " INTERPRETATION:\n", inspectOutput
|
||||
continue
|
||||
else:
|
||||
if input.kind == JString and input.str[0] == '#':
|
||||
continue
|
||||
|
||||
var outRlp = initRlpWriter()
|
||||
outRlp.append input
|
||||
let
|
||||
actual = outRlp.finish.hexRepr
|
||||
expected = output.str
|
||||
if actual != expected:
|
||||
testStatus "FAILED"
|
||||
echo " EXPECTED BYTES: ", expected
|
||||
echo " ACTUAL BYTES: ", actual
|
||||
continue
|
||||
|
||||
testStatus "OK"
|
||||
|
Loading…
Reference in New Issue