mirror of
https://github.com/status-im/nim-rlp.git
synced 2025-02-19 17:34:26 +00:00
Fixes for bugs discovered while testing the Hexary Trie
Bugfixes: * `rlp.rawData` was returning a wider than necessary range when called over a `listElem` result. * Appending `Rlp` objects to a `RlpWriter` was producing incorrect results. New features: * When the Rlp reading cursor is positioned over a list, you can call `enterList` to position the cursor to the first list element. Call `skipElem` to move the cursor further. * `rlpFromHex` will now accept leading `0x` symbols (often used in the Ethereum world). * `rlp.inspect` will now print hex characters by default. Use the optional parameter `hexOutput` to control the behavior.
This commit is contained in:
parent
e0d62ae9ba
commit
c1b1766f86
41
rlp.nim
41
rlp.nim
@ -41,12 +41,15 @@ proc rlpFromHex*(input: string): Rlp =
|
|||||||
doAssert input.len mod 2 == 0,
|
doAssert input.len mod 2 == 0,
|
||||||
"rlpFromHex expects a string with even number of characters (assuming two characters per byte)"
|
"rlpFromHex expects a string with even number of characters (assuming two characters per byte)"
|
||||||
|
|
||||||
let totalBytes = input.len div 2
|
var startByte = if input.len >= 2 and input[0] == '0' and input[1] == 'x': 2
|
||||||
|
else: 0
|
||||||
|
|
||||||
|
let totalBytes = (input.len - startByte) div 2
|
||||||
var backingStore = newSeq[byte](totalBytes)
|
var backingStore = newSeq[byte](totalBytes)
|
||||||
|
|
||||||
for i in 0 ..< totalBytes:
|
for i in 0 ..< totalBytes:
|
||||||
var nextByte: int
|
var nextByte: int
|
||||||
if parseHex(input, nextByte, i*2, 2) == 2:
|
if parseHex(input, nextByte, startByte + i*2, 2) == 2:
|
||||||
backingStore[i] = byte(nextByte)
|
backingStore[i] = byte(nextByte)
|
||||||
else:
|
else:
|
||||||
doAssert false, "rlpFromHex expects a hexademical string, but the input contains non hexademical characters"
|
doAssert false, "rlpFromHex expects a hexademical string, but the input contains non hexademical characters"
|
||||||
@ -58,8 +61,10 @@ proc rlpFromHex*(input: string): Rlp =
|
|||||||
proc hasData*(self: Rlp): bool =
|
proc hasData*(self: Rlp): bool =
|
||||||
position < bytes.len
|
position < bytes.len
|
||||||
|
|
||||||
template rawData*(self: Rlp): BytesRange =
|
proc currentElemEnd(self: Rlp): int
|
||||||
self.bytes
|
|
||||||
|
proc rawData*(self: Rlp): BytesRange =
|
||||||
|
return self.bytes[position ..< self.currentElemEnd]
|
||||||
|
|
||||||
proc isBlob*(self: Rlp): bool =
|
proc isBlob*(self: Rlp): bool =
|
||||||
hasData() and bytes[position] < LIST_START_MARKER
|
hasData() and bytes[position] < LIST_START_MARKER
|
||||||
@ -209,7 +214,8 @@ proc toString*(self: Rlp): string =
|
|||||||
|
|
||||||
proc toBytes*(self: Rlp): BytesRange =
|
proc toBytes*(self: Rlp): BytesRange =
|
||||||
if not isBlob():
|
if not isBlob():
|
||||||
raise newException(RlpTypeMismatch, "Bytes expected, but the source RLP in not a blob")
|
raise newException(RlpTypeMismatch,
|
||||||
|
"Bytes expected, but the source RLP in not a blob")
|
||||||
|
|
||||||
let
|
let
|
||||||
payloadOffset = payloadOffset()
|
payloadOffset = payloadOffset()
|
||||||
@ -220,16 +226,18 @@ proc toBytes*(self: Rlp): BytesRange =
|
|||||||
result = bytes.slice(ibegin, iend)
|
result = bytes.slice(ibegin, iend)
|
||||||
|
|
||||||
proc currentElemEnd(self: Rlp): int =
|
proc currentElemEnd(self: Rlp): int =
|
||||||
|
assert hasData()
|
||||||
result = position
|
result = position
|
||||||
|
|
||||||
if not hasData():
|
|
||||||
return
|
|
||||||
|
|
||||||
if isSingleByte():
|
if isSingleByte():
|
||||||
result += 1
|
result += 1
|
||||||
elif isBlob() or isList():
|
elif isBlob() or isList():
|
||||||
result += payloadOffset() + payloadBytesCount()
|
result += payloadOffset() + payloadBytesCount()
|
||||||
|
|
||||||
|
proc enterList*(self: var Rlp) =
|
||||||
|
assert isList()
|
||||||
|
position += payloadOffset()
|
||||||
|
|
||||||
proc skipElem*(rlp: var Rlp) =
|
proc skipElem*(rlp: var Rlp) =
|
||||||
rlp.position = rlp.currentElemEnd
|
rlp.position = rlp.currentElemEnd
|
||||||
|
|
||||||
@ -376,7 +384,7 @@ template decode*(bytes: openarray[byte], T: typedesc): T =
|
|||||||
decode(initBytesRange(bytesCopy), T)
|
decode(initBytesRange(bytesCopy), T)
|
||||||
|
|
||||||
proc append*(writer: var RlpWriter; rlp: Rlp) =
|
proc append*(writer: var RlpWriter; rlp: Rlp) =
|
||||||
append(writer, rlp.rawData)
|
appendRawBytes(writer, rlp.rawData)
|
||||||
|
|
||||||
proc isPrintable(s: string): bool =
|
proc isPrintable(s: string): bool =
|
||||||
for c in s:
|
for c in s:
|
||||||
@ -385,7 +393,7 @@ proc isPrintable(s: string): bool =
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc inspectAux(self: var Rlp, depth: int, output: var string) =
|
proc inspectAux(self: var Rlp, depth: int, hexOutput: bool, output: var string) =
|
||||||
if not hasData():
|
if not hasData():
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -407,19 +415,26 @@ proc inspectAux(self: var Rlp, depth: int, output: var string) =
|
|||||||
else:
|
else:
|
||||||
output.add "blob(" & $str.len & ") ["
|
output.add "blob(" & $str.len & ") ["
|
||||||
for c in str:
|
for c in str:
|
||||||
|
if hexOutput:
|
||||||
|
output.add toHex(int(c), 2)
|
||||||
|
else:
|
||||||
output.add $ord(c)
|
output.add $ord(c)
|
||||||
output.add ","
|
output.add ","
|
||||||
|
|
||||||
|
if hexOutput:
|
||||||
|
output.add ']'
|
||||||
|
else:
|
||||||
output[^1] = ']'
|
output[^1] = ']'
|
||||||
else:
|
else:
|
||||||
output.add "{\n"
|
output.add "{\n"
|
||||||
for subitem in self:
|
for subitem in self:
|
||||||
inspectAux(subitem, depth + 1, output)
|
inspectAux(subitem, depth + 1, hexOutput, output)
|
||||||
output.add "\n"
|
output.add "\n"
|
||||||
indent()
|
indent()
|
||||||
output.add "}"
|
output.add "}"
|
||||||
|
|
||||||
proc inspect*(self: Rlp, indent = 0): string =
|
proc inspect*(self: Rlp, indent = 0, hexOutput = true): string =
|
||||||
var rlpCopy = self
|
var rlpCopy = self
|
||||||
result = newStringOfCap(bytes.len)
|
result = newStringOfCap(bytes.len)
|
||||||
inspectAux(rlpCopy, indent, result)
|
inspectAux(rlpCopy, indent, hexOutput, result)
|
||||||
|
|
||||||
|
@ -120,6 +120,10 @@ proc appendRawList(self; bytes: BytesRange) =
|
|||||||
output.add(bytes)
|
output.add(bytes)
|
||||||
maybeClosePendingLists()
|
maybeClosePendingLists()
|
||||||
|
|
||||||
|
proc appendRawBytes*(self; bytes: BytesRange) =
|
||||||
|
output.add(bytes)
|
||||||
|
maybeClosePendingLists()
|
||||||
|
|
||||||
proc startList*(self; listSize: int) =
|
proc startList*(self; listSize: int) =
|
||||||
if listSize == 0:
|
if listSize == 0:
|
||||||
appendRawList(BytesRange())
|
appendRawList(BytesRange())
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
import
|
import
|
||||||
test_api_usage, test_json_suite, test_object_serialization
|
test_api_usage, test_object_serialization, test_json_suite
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ test "empty bytes are not a proper RLP":
|
|||||||
not rlp.isList
|
not rlp.isList
|
||||||
not rlp.isEmpty
|
not rlp.isEmpty
|
||||||
|
|
||||||
|
expect Exception:
|
||||||
|
rlp.skipElem
|
||||||
|
|
||||||
expect Exception:
|
expect Exception:
|
||||||
discard rlp.getType
|
discard rlp.getType
|
||||||
|
|
||||||
@ -95,6 +98,18 @@ test "encode and decode lists":
|
|||||||
rlp.listElem(1).toString == "Lorem ipsum dolor sit amet"
|
rlp.listElem(1).toString == "Lorem ipsum dolor sit amet"
|
||||||
rlp.listElem(2).toString == "Donec ligula tortor, egestas eu est vitae"
|
rlp.listElem(2).toString == "Donec ligula tortor, egestas eu est vitae"
|
||||||
|
|
||||||
|
# test creating RLPs from other RLPs
|
||||||
|
var list = rlpFromBytes encodeList(rlp.listELem(1), rlp.listELem(0))
|
||||||
|
|
||||||
|
# test that iteration with enterList/skipElem works as expected
|
||||||
|
list.enterList
|
||||||
|
check list.toString == "Lorem ipsum dolor sit amet"
|
||||||
|
list.skipElem
|
||||||
|
check list.toInt(int32) == 6000.int32
|
||||||
|
list.skipElem
|
||||||
|
check(not list.hasData)
|
||||||
|
expect Exception: list.skipElem
|
||||||
|
|
||||||
test "toBytes":
|
test "toBytes":
|
||||||
let rlp = rlpFromHex("f2cb847f000001827666827666a040ef02798f211da2e8173d37f255be908871ae65060dbb2f77fb29c0421447f4845ab90b50")
|
let rlp = rlpFromHex("f2cb847f000001827666827666a040ef02798f211da2e8173d37f255be908871ae65060dbb2f77fb29c0421447f4845ab90b50")
|
||||||
let tok = rlp.listElem(1).toBytes()
|
let tok = rlp.listElem(1).toBytes()
|
||||||
@ -125,8 +140,12 @@ test "encoding length":
|
|||||||
check emptyListRlp.blobLen == 0
|
check emptyListRlp.blobLen == 0
|
||||||
|
|
||||||
test "basic decoding":
|
test "basic decoding":
|
||||||
var rlp = rlpFromHex("856d6f6f7365")
|
var rlp1 = rlpFromHex("856d6f6f7365")
|
||||||
check rlp.inspect == q"moose"
|
var rlp2 = rlpFromHex("0x856d6f6f7365")
|
||||||
|
|
||||||
|
check:
|
||||||
|
rlp1.inspect == q"moose"
|
||||||
|
rlp2.inspect == q"moose"
|
||||||
|
|
||||||
test "malformed/truncated RLP":
|
test "malformed/truncated RLP":
|
||||||
var rlp = rlpFromHex("b8056d6f6f7365")
|
var rlp = rlpFromHex("b8056d6f6f7365")
|
||||||
@ -144,3 +163,7 @@ test "encode byte arrays":
|
|||||||
rlp.listElem(0).toBytes().toSeq() == @b1
|
rlp.listElem(0).toBytes().toSeq() == @b1
|
||||||
rlp.listElem(1).toBytes().toSeq() == @b2
|
rlp.listElem(1).toBytes().toSeq() == @b2
|
||||||
rlp.listElem(2).toBytes().toSeq() == @b3
|
rlp.listElem(2).toBytes().toSeq() == @b3
|
||||||
|
|
||||||
|
# The first byte here is the length of the datum (132 - 128 => 4)
|
||||||
|
$(rlp.listElem(1).rawData) == "R[132, 6, 8, 12, 123]"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user