2020-04-22 05:53:02 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
{.pragma: raisesssz, raises: [Defect, MalformedSszError, SszSizeMismatchError].}
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
import
|
2020-03-05 00:29:27 +00:00
|
|
|
typetraits, options,
|
|
|
|
stew/[bitseqs, endians2, objects, bitseqs], serialization/testing/tracing,
|
2019-07-03 07:35:05 +00:00
|
|
|
../spec/[digest, datatypes], ./types
|
|
|
|
|
2020-01-24 18:35:15 +00:00
|
|
|
const
|
|
|
|
maxListAllocation = 1 * 1024 * 1024 * 1024 # 1 GiB
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
template raiseIncorrectSize(T: type) =
|
|
|
|
const typeName = name(T)
|
|
|
|
raise newException(MalformedSszError,
|
|
|
|
"SSZ " & typeName & " input of incorrect size")
|
|
|
|
|
2020-01-24 18:35:15 +00:00
|
|
|
template setOutputSize[R, T](a: var array[R, T], length: int) =
|
2019-07-03 07:35:05 +00:00
|
|
|
if length != a.len:
|
2020-05-22 19:55:00 +00:00
|
|
|
raiseIncorrectSize a.type
|
2020-01-24 18:35:15 +00:00
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
proc setOutputSize(list: var List, length: int) {.inline, raisesssz.} =
|
|
|
|
if int64(length) > list.maxLen:
|
|
|
|
raise newException(MalformedSszError, "SSZ list maximum size exceeded")
|
|
|
|
list.setLen length
|
2020-01-24 18:35:15 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
# fromSszBytes copies the wire representation to a Nim variable,
|
|
|
|
# assuming there's enough data in the buffer
|
2020-05-22 19:55:00 +00:00
|
|
|
func fromSszBytes*(T: type UintN, data: openarray[byte]): T {.raisesssz.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
## Convert directly to bytes the size of the int. (e.g. ``uint16 = 2 bytes``)
|
|
|
|
## All integers are serialized as **little endian**.
|
2020-05-22 19:55:00 +00:00
|
|
|
if data.len != sizeof(result):
|
|
|
|
raiseIncorrectSize T
|
2020-01-24 18:35:15 +00:00
|
|
|
|
2020-03-05 00:29:27 +00:00
|
|
|
T.fromBytesLE(data)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
func fromSszBytes*(T: type bool, data: openarray[byte]): T {.raisesssz.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
# TODO: spec doesn't say what to do if the value is >1 - we'll use the C
|
|
|
|
# definition for now, but maybe this should be a parse error instead?
|
2020-05-22 19:55:00 +00:00
|
|
|
if data.len != 1 or byte(data[0]) > byte(1):
|
2020-01-24 18:35:15 +00:00
|
|
|
raise newException(MalformedSszError, "invalid boolean value")
|
|
|
|
data[0] == 1
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
func fromSszBytes*(T: type Eth2Digest, data: openarray[byte]): T {.raisesssz.} =
|
2020-05-22 19:55:00 +00:00
|
|
|
if data.len != sizeof(result.data):
|
|
|
|
raiseIncorrectSize T
|
2019-07-03 07:35:05 +00:00
|
|
|
copyMem(result.data.addr, unsafeAddr data[0], sizeof(result.data))
|
|
|
|
|
|
|
|
template fromSszBytes*(T: type Slot, bytes: openarray[byte]): Slot =
|
|
|
|
Slot fromSszBytes(uint64, bytes)
|
|
|
|
|
|
|
|
template fromSszBytes*(T: type Epoch, bytes: openarray[byte]): Epoch =
|
|
|
|
Epoch fromSszBytes(uint64, bytes)
|
|
|
|
|
2020-05-11 18:08:52 +00:00
|
|
|
func fromSszBytes*(T: type ForkDigest, bytes: openarray[byte]): T {.raisesssz.} =
|
2020-05-22 19:55:00 +00:00
|
|
|
if bytes.len != sizeof(result):
|
|
|
|
raiseIncorrectSize T
|
2020-05-11 18:08:52 +00:00
|
|
|
copyMem(result.addr, unsafeAddr bytes[0], sizeof(result))
|
|
|
|
|
|
|
|
func fromSszBytes*(T: type Version, bytes: openarray[byte]): T {.raisesssz.} =
|
2020-05-22 19:55:00 +00:00
|
|
|
if bytes.len != sizeof(result):
|
|
|
|
raiseIncorrectSize T
|
2020-05-11 18:08:52 +00:00
|
|
|
copyMem(result.addr, unsafeAddr bytes[0], sizeof(result))
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
template fromSszBytes*(T: type enum, bytes: openarray[byte]): auto =
|
2019-07-03 07:35:05 +00:00
|
|
|
T fromSszBytes(uint64, bytes)
|
|
|
|
|
|
|
|
template fromSszBytes*(T: type BitSeq, bytes: openarray[byte]): auto =
|
|
|
|
BitSeq @bytes
|
|
|
|
|
2020-04-15 01:24:13 +00:00
|
|
|
proc `[]`[T, U, V](s: openArray[T], x: HSlice[U, V]) {.error:
|
|
|
|
"Please don't use openarray's [] as it allocates a result sequence".}
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
# func readOpenArray[T](result: var openarray[T], input: openarray[byte]) =
|
|
|
|
|
|
|
|
template checkForForbiddenBits(ResulType: type,
|
|
|
|
input: openarray[byte],
|
|
|
|
expectedBits: static int) =
|
|
|
|
## This checks if the input contains any bits set above the maximum
|
|
|
|
## sized allowed. We only need to check the last byte to verify this:
|
|
|
|
const bitsInLastByte = (expectedBits mod 8)
|
|
|
|
when bitsInLastByte != 0:
|
|
|
|
# As an example, if there are 3 bits expected in the last byte,
|
|
|
|
# we calculate a bitmask equal to 11111000. If the input has any
|
|
|
|
# raised bits in range of the bitmask, this would be a violation
|
|
|
|
# of the size of the BitArray:
|
|
|
|
const forbiddenBitsMask = byte(byte(0xff) shl bitsInLastByte)
|
|
|
|
|
|
|
|
if (input[^1] and forbiddenBitsMask) != 0:
|
|
|
|
raiseIncorrectSize ResulType
|
|
|
|
|
2020-04-22 05:53:02 +00:00
|
|
|
func readSszValue*(input: openarray[byte], T: type): T {.raisesssz.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
mixin fromSszBytes, toSszType
|
|
|
|
|
2020-01-24 18:35:15 +00:00
|
|
|
type T {.used.} = type(result)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
template readOffsetUnchecked(n: int): int {.used.}=
|
2020-04-15 01:24:13 +00:00
|
|
|
int fromSszBytes(uint32, input.toOpenArray(n, n + offsetSize - 1))
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
template readOffset(n: int): int {.used.} =
|
|
|
|
let offset = readOffsetUnchecked(n)
|
|
|
|
if offset > input.len:
|
|
|
|
raise newException(MalformedSszError, "SSZ list element offset points past the end of the input")
|
|
|
|
offset
|
|
|
|
|
|
|
|
#when result is List:
|
|
|
|
# result.setOutputSize input.len
|
|
|
|
# readOpenArray(toSeq result, input)
|
|
|
|
|
|
|
|
#elif result is array:
|
|
|
|
# result.checkOutputSize input.len
|
|
|
|
# readOpenArray(result, input)
|
2020-01-24 18:35:15 +00:00
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
when result is BitList:
|
|
|
|
if input.len == 0:
|
|
|
|
raise newException(MalformedSszError, "Invalid empty SSZ BitList value")
|
|
|
|
|
|
|
|
const maxExpectedSize = (result.maxLen div 8) + 1
|
|
|
|
result = T readSszValue(input, List[byte, maxExpectedSize])
|
|
|
|
|
|
|
|
let resultBytesCount = len bytes(result)
|
|
|
|
|
|
|
|
if bytes(result)[resultBytesCount - 1] == 0:
|
|
|
|
raise newException(MalformedSszError, "SSZ BitList is not properly terminated")
|
|
|
|
|
|
|
|
if resultBytesCount == maxExpectedSize:
|
|
|
|
checkForForbiddenBits(T, input, result.maxLen + 1)
|
|
|
|
|
|
|
|
elif result is List|array:
|
2019-07-03 07:35:05 +00:00
|
|
|
type ElemType = type result[0]
|
2020-05-22 19:55:00 +00:00
|
|
|
when ElemType is byte:
|
2020-01-24 18:35:15 +00:00
|
|
|
result.setOutputSize input.len
|
2019-09-27 16:48:12 +00:00
|
|
|
if input.len > 0:
|
|
|
|
copyMem(addr result[0], unsafeAddr input[0], input.len)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
elif isFixedSize(ElemType):
|
|
|
|
const elemSize = fixedPortionSize(ElemType)
|
|
|
|
if input.len mod elemSize != 0:
|
|
|
|
var ex = new SszSizeMismatchError
|
|
|
|
ex.deserializedType = cstring typetraits.name(T)
|
|
|
|
ex.actualSszSize = input.len
|
|
|
|
ex.elementSize = elemSize
|
|
|
|
raise ex
|
2020-01-24 18:35:15 +00:00
|
|
|
result.setOutputSize input.len div elemSize
|
2019-07-03 07:35:05 +00:00
|
|
|
trs "READING LIST WITH LEN ", result.len
|
|
|
|
for i in 0 ..< result.len:
|
|
|
|
trs "TRYING TO READ LIST ELEM ", i
|
|
|
|
let offset = i * elemSize
|
2020-04-15 01:24:13 +00:00
|
|
|
result[i] = readSszValue(input.toOpenArray(offset, offset + elemSize - 1), ElemType)
|
2019-07-03 07:35:05 +00:00
|
|
|
trs "LIST READING COMPLETE"
|
|
|
|
|
|
|
|
else:
|
|
|
|
if input.len == 0:
|
|
|
|
# This is an empty list.
|
|
|
|
# The default initialization of the return value is fine.
|
|
|
|
return
|
2020-01-24 18:35:15 +00:00
|
|
|
elif input.len < offsetSize:
|
|
|
|
raise newException(MalformedSszError, "SSZ input of insufficient size")
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
var offset = readOffset 0
|
2020-05-22 19:55:00 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
trs "GOT OFFSET ", offset
|
|
|
|
let resultLen = offset div offsetSize
|
|
|
|
trs "LEN ", resultLen
|
2020-04-23 19:39:23 +00:00
|
|
|
|
|
|
|
if resultLen == 0:
|
|
|
|
# If there are too many elements, other constraints detect problems
|
|
|
|
# (not monotonically increasing, past end of input, or last element
|
|
|
|
# not matching up with its nextOffset properly)
|
|
|
|
raise newException(MalformedSszError, "SSZ list incorrectly encoded of zero length")
|
|
|
|
|
2020-01-24 18:35:15 +00:00
|
|
|
result.setOutputSize resultLen
|
2019-07-03 07:35:05 +00:00
|
|
|
for i in 1 ..< resultLen:
|
|
|
|
let nextOffset = readOffset(i * offsetSize)
|
2020-01-24 18:35:15 +00:00
|
|
|
if nextOffset <= offset:
|
|
|
|
raise newException(MalformedSszError, "SSZ list element offsets are not monotonically increasing")
|
2019-08-05 00:00:49 +00:00
|
|
|
else:
|
2020-04-15 01:24:13 +00:00
|
|
|
result[i - 1] = readSszValue(input.toOpenArray(offset, nextOffset - 1), ElemType)
|
2019-07-03 07:35:05 +00:00
|
|
|
offset = nextOffset
|
|
|
|
|
2020-04-15 01:24:13 +00:00
|
|
|
result[resultLen - 1] = readSszValue(input.toOpenArray(offset, input.len - 1), ElemType)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
# TODO: Should be possible to remove BitArray from here
|
|
|
|
elif result is UintN|bool|enum:
|
2020-01-29 00:40:27 +00:00
|
|
|
trs "READING BASIC TYPE ", type(result).name, " input=", input.len
|
|
|
|
result = fromSszBytes(type(result), input)
|
|
|
|
trs "RESULT WAS ", repr(result)
|
|
|
|
|
2020-05-22 19:55:00 +00:00
|
|
|
elif result is BitArray:
|
|
|
|
if sizeof(result) != input.len:
|
|
|
|
raiseIncorrectSize T
|
|
|
|
checkForForbiddenBits(T, input, result.bits)
|
|
|
|
copyMem(addr result.bytes[0], unsafeAddr input[0], input.len)
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
elif result is object|tuple:
|
2020-01-24 18:35:15 +00:00
|
|
|
const minimallyExpectedSize = fixedPortionSize(T)
|
|
|
|
if input.len < minimallyExpectedSize:
|
|
|
|
raise newException(MalformedSszError, "SSZ input of insufficient size")
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
enumInstanceSerializedFields(result, fieldName, field):
|
|
|
|
const boundingOffsets = T.getFieldBoundingOffsets(fieldName)
|
|
|
|
trs "BOUNDING OFFSET FOR FIELD ", fieldName, " = ", boundingOffsets
|
|
|
|
|
2020-04-29 20:12:07 +00:00
|
|
|
type FieldType = type field
|
2020-04-22 23:35:55 +00:00
|
|
|
type SszType = type toSszType(declval FieldType)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
when isFixedSize(SszType):
|
|
|
|
const
|
|
|
|
startOffset = boundingOffsets[0]
|
|
|
|
endOffset = boundingOffsets[1]
|
|
|
|
trs "FIXED FIELD ", startOffset, "-", endOffset
|
|
|
|
else:
|
|
|
|
let
|
2020-05-22 19:55:00 +00:00
|
|
|
startOffset = readOffsetUnchecked(boundingOffsets[0])
|
2019-07-03 07:35:05 +00:00
|
|
|
endOffset = if boundingOffsets[1] == -1: input.len
|
2020-05-22 19:55:00 +00:00
|
|
|
else: readOffsetUnchecked(boundingOffsets[1])
|
2019-07-03 07:35:05 +00:00
|
|
|
trs "VAR FIELD ", startOffset, "-", endOffset
|
2020-01-27 17:20:06 +00:00
|
|
|
if startOffset > endOffset:
|
2020-01-24 18:35:15 +00:00
|
|
|
raise newException(MalformedSszError, "SSZ field offsets are not monotonically increasing")
|
|
|
|
elif endOffset > input.len:
|
|
|
|
raise newException(MalformedSszError, "SSZ field offset points past the end of the input")
|
2020-04-17 09:00:47 +00:00
|
|
|
elif startOffset < minimallyExpectedSize:
|
2020-04-16 19:21:28 +00:00
|
|
|
raise newException(MalformedSszError, "SSZ field offset points outside bounding offsets")
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
# TODO The extra type escaping here is a work-around for a Nim issue:
|
|
|
|
when type(FieldType) is type(SszType):
|
|
|
|
trs "READING NATIVE ", fieldName, ": ", name(SszType)
|
2020-05-22 19:55:00 +00:00
|
|
|
field = typeof(field) readSszValue(
|
2020-04-22 23:35:55 +00:00
|
|
|
input.toOpenArray(startOffset, endOffset - 1),
|
|
|
|
SszType)
|
2019-07-03 07:35:05 +00:00
|
|
|
trs "READING COMPLETE ", fieldName
|
2020-04-22 23:35:55 +00:00
|
|
|
|
2020-05-18 17:49:22 +00:00
|
|
|
elif FieldType is List:
|
|
|
|
# TODO
|
|
|
|
# The `typeof(field)` coercion below is required to deal with a Nim
|
|
|
|
# bug. For some reason, Nim gets confused about the type of the list
|
|
|
|
# returned from the `readSszValue` function. This could be a generics
|
|
|
|
# caching issue caused by the use of distinct types. Such an issue
|
|
|
|
# would be very scary in general, but in this particular situation
|
|
|
|
# it shouldn't matter, because the different flavours of `List[T, N]`
|
|
|
|
# won't produce different serializations.
|
|
|
|
field = typeof(field) readSszValue(
|
2020-04-22 23:35:55 +00:00
|
|
|
input.toOpenArray(startOffset, endOffset - 1),
|
|
|
|
FieldType)
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
else:
|
|
|
|
trs "READING FOREIGN ", fieldName, ": ", name(SszType)
|
2020-04-29 20:12:07 +00:00
|
|
|
field = fromSszBytes(
|
2020-04-22 23:35:55 +00:00
|
|
|
FieldType,
|
|
|
|
input.toOpenArray(startOffset, endOffset - 1))
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
unsupported T
|