nim-serde/serde/cbor/deserializer.nim

646 lines
19 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# This file is a modified version of Emery Hemingways CBOR library for Nim,
# originally available at https://github.com/ehmry/cbor-nim and released under The Unlicense.
import std/[math, streams, options, tables, strutils, times, typetraits, macros]
import ./types as cborTypes
import ./helpers
import ../utils/types
import ../utils/pragmas
import ./errors
import pkg/questionable
import pkg/questionable/results
export results
export types
export pragmas
export cborTypes
export macros
{.push raises: [].}
func isIndefinite*(c: CborParser): bool {.inline.} =
## Return true if the parser is positioned on an item of indefinite length.
c.minor == 31
proc open*(c: var CborParser, s: Stream) =
## Begin parsing a stream of CBOR in binary form.
## The parser will be initialized in an EOF state, call
## ``next`` to advance it before parsing.
c.s = s
c.kind = cborEof
c.intVal = 0
proc next*(c: var CborParser) {.raises: [CborParseError].} =
## Advance the parser to the initial or next event.
try:
if c.s.atEnd:
c.kind = CborEventKind.cborEof
c.intVal = 0
else:
let
ib = c.s.readUint8
mb = ib shr 5
c.minor = ib and 0b11111
case c.minor
of 0 .. 23:
c.intVal = c.minor.uint64
of 24:
c.intVal = c.s.readChar.uint64
of 25:
c.intVal = c.s.readChar.uint64
c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
of 26:
c.intVal = c.s.readChar.uint64
for _ in 1 .. 3:
{.unroll.}
c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
of 27:
c.intVal = c.s.readChar.uint64
for _ in 1 .. 7:
{.unroll.}
c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
else:
c.intVal = 0
case mb
of PositiveMajor:
c.kind = CborEventKind.cborPositive
of NegativeMajor:
c.kind = CborEventKind.cborNegative
of BytesMajor:
c.kind = CborEventKind.cborBytes
of TextMajor:
c.kind = CborEventKind.cborText
of ArrayMajor:
c.kind = CborEventKind.cborArray
of MapMajor:
c.kind = CborEventKind.cborMap
of TagMajor:
c.kind = CborEventKind.cborTag
of SimpleMajor:
if c.minor in {25, 26, 27}:
c.kind = CborEventKind.cborFloat
elif c.isIndefinite:
c.kind = CborEventKind.cborBreak
else:
c.kind = CborEventKind.cborSimple
else:
raise newCborError("unhandled major type " & $mb)
except IOError as e:
raise newException(CborParseError, e.msg, e)
except OSError as e:
raise newException(CborParseError, e.msg, e)
proc nextUInt*(c: var CborParser): BiggestUInt {.raises: [CborParseError].} =
## Parse the integer value that the parser is positioned on.
parseAssert(
c.kind == CborEventKind.cborPositive, "Expected positive integer, got " & $c.kind
)
result = c.intVal.BiggestUInt
c.next()
proc nextInt*(c: var CborParser): BiggestInt {.raises: [CborParseError].} =
## Parse the integer value that the parser is positioned on.
case c.kind
of CborEventKind.cborPositive:
result = c.intVal.BiggestInt
of CborEventKind.cborNegative:
result = -1.BiggestInt - c.intVal.BiggestInt
else:
raise newCborError("Expected integer, got " & $c.kind)
c.next()
proc nextFloat*(c: var CborParser): float64 {.raises: [CborParseError].} =
## Parse the float value that the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborFloat, "Expected float, got " & $c.kind)
case c.minor
of 25:
result = floatSingle(c.intVal.uint16).float64
of 26:
result = cast[float32](c.intVal).float64
of 27:
result = cast[float64](c.intVal)
else:
discard
c.next()
func bytesLen*(c: CborParser): int {.raises: [CborParseError].} =
## Return the length of the byte string that the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind)
c.intVal.int
proc nextBytes*(
c: var CborParser, buf: var openArray[byte]
) {.raises: [CborParseError].} =
## Read the bytes that the parser is positioned on and advance.
try:
parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind)
parseAssert(
buf.len == c.intVal.int,
"Buffer length mismatch: expected " & $c.intVal.int & ", got " & $buf.len,
)
if buf.len > 0:
let n = c.s.readData(buf[0].addr, buf.len)
parseAssert(n == buf.len, "truncated read of CBOR data")
c.next()
except OSError as e:
raise newException(CborParseError, e.msg, e)
except IOError as e:
raise newException(CborParseError, e.msg, e)
proc nextBytes*(c: var CborParser): seq[byte] {.raises: [CborParseError].} =
## Read the bytes that the parser is positioned on into a seq and advance.
result = newSeq[byte](c.intVal.int)
nextBytes(c, result)
func textLen*(c: CborParser): int {.raises: [CborParseError].} =
## Return the length of the text that the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
c.intVal.int
proc nextText*(c: var CborParser, buf: var string) {.raises: [CborParseError].} =
## Read the text that the parser is positioned on into a string and advance.
try:
parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
buf.setLen c.intVal.int
if buf.len > 0:
let n = c.s.readData(buf[0].addr, buf.len)
parseAssert(n == buf.len, "truncated read of CBOR data")
c.next()
except IOError as e:
raise newException(CborParseError, e.msg, e)
except OSError as e:
raise newException(CborParseError, e.msg, e)
proc nextText*(c: var CborParser): string {.raises: [CborParseError].} =
## Read the text that the parser is positioned on into a string and advance.
nextText(c, result)
func arrayLen*(c: CborParser): int {.raises: [CborParseError].} =
## Return the length of the array that the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborArray, "Expected array, got " & $c.kind)
c.intVal.int
func mapLen*(c: CborParser): int {.raises: [CborParseError].} =
## Return the length of the map that the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborMap, "Expected map, got " & $c.kind)
c.intVal.int
func tag*(c: CborParser): uint64 {.raises: [CborParseError].} =
## Return the tag value the parser is positioned on.
parseAssert(c.kind == CborEventKind.cborTag, "Expected tag, got " & $c.kind)
c.intVal
proc skipNode*(c: var CborParser) {.raises: [CborParseError].} =
## Skip the item the parser is positioned on.
try:
case c.kind
of CborEventKind.cborEof:
raise newCborError("end of CBOR stream")
of CborEventKind.cborPositive, CborEventKind.cborNegative, CborEventKind.cborSimple:
c.next()
of CborEventKind.cborBytes, CborEventKind.cborText:
if c.isIndefinite:
c.next()
while c.kind != CborEventKind.cborBreak:
parseAssert(
c.kind == CborEventKind.cborBytes, "expected bytes, got " & $c.kind
)
for _ in 1 .. c.intVal.int:
discard readChar(c.s)
c.next()
else:
for _ in 1 .. c.intVal.int:
discard readChar(c.s)
c.next()
of CborEventKind.cborArray:
if c.isIndefinite:
c.next()
while c.kind != CborEventKind.cborBreak:
c.skipNode()
c.next()
else:
let len = c.intVal
c.next()
for i in 1 .. len:
c.skipNode()
of CborEventKind.cborMap:
let mapLen = c.intVal.int
if c.isIndefinite:
c.next()
while c.kind != CborEventKind.cborBreak:
c.skipNode()
c.next()
else:
c.next()
for _ in 1 .. mapLen:
c.skipNode()
of CborEventKind.cborTag:
c.next()
c.skipNode()
of CborEventKind.cborFloat:
discard c.nextFloat()
of CborEventKind.cborBreak:
discard
except OSError as os:
raise newException(CborParseError, os.msg, os)
except IOError as io:
raise newException(CborParseError, io.msg, io)
proc nextNode*(c: var CborParser): CborNode {.raises: [CborParseError].} =
## Parse the item the parser is positioned on into a ``CborNode``.
## This is cheap for numbers or simple values but expensive
## for nested types.
try:
case c.kind
of CborEventKind.cborEof:
raise newCborError("end of CBOR stream")
of CborEventKind.cborPositive:
result = CborNode(kind: cborUnsigned, uint: c.intVal)
c.next()
of CborEventKind.cborNegative:
result = CborNode(kind: cborNegative, int: -1 - c.intVal.int64)
c.next()
of CborEventKind.cborBytes:
if c.isIndefinite:
result = CborNode(kind: cborBytes, bytes: newSeq[byte]())
c.next()
while c.kind != CborEventKind.cborBreak:
parseAssert(
c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind
)
let
chunkLen = c.intVal.int
pos = result.bytes.len
result.bytes.setLen(pos + chunkLen)
let n = c.s.readData(result.bytes[pos].addr, chunkLen)
parseAssert(n == chunkLen, "truncated read of CBOR data")
c.next()
else:
result = CborNode(kind: cborBytes, bytes: c.nextBytes())
of CborEventKind.cborText:
if c.isIndefinite:
result = CborNode(kind: cborText, text: "")
c.next()
while c.kind != CborEventKind.cborBreak:
parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
let
chunkLen = c.intVal.int
pos = result.text.len
result.text.setLen(pos + chunkLen)
let n = c.s.readData(result.text[pos].addr, chunkLen)
parseAssert(n == chunkLen, "truncated read of CBOR data")
c.next()
c.next()
else:
result = CborNode(kind: cborText, text: c.nextText())
of CborEventKind.cborArray:
result = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal))
if c.isIndefinite:
c.next()
while c.kind != CborEventKind.cborBreak:
result.seq.add(c.nextNode())
c.next()
else:
c.next()
for i in 0 .. result.seq.high:
result.seq[i] = c.nextNode()
of CborEventKind.cborMap:
let mapLen = c.intVal.int
result = CborNode(
kind: cborMap, map: initOrderedTable[CborNode, CborNode](mapLen.nextPowerOfTwo)
)
if c.isIndefinite:
c.next()
while c.kind != CborEventKind.cborBreak:
result.map[c.nextNode()] = c.nextNode()
c.next()
else:
c.next()
for _ in 1 .. mapLen:
result.map[c.nextNode()] = c.nextNode()
of CborEventKind.cborTag:
let tag = c.intVal
c.next()
result = c.nextNode()
result.tag = some tag
of CborEventKind.cborSimple:
case c.minor
of 24:
result = CborNode(kind: cborSimple, simple: c.intVal.uint8)
else:
result = CborNode(kind: cborSimple, simple: c.minor)
c.next()
of CborEventKind.cborFloat:
result = CborNode(kind: cborFloat, float: c.nextFloat())
of CborEventKind.cborBreak:
discard
except OSError as e:
raise newException(CborParseError, e.msg, e)
except IOError as e:
raise newException(CborParseError, e.msg, e)
except CatchableError as e:
raise newException(CborParseError, e.msg, e)
except Exception as e:
raise newException(Defect, e.msg, e)
proc readCbor*(s: Stream): CborNode {.raises: [CborParseError].} =
## Parse a stream into a CBOR object.
var parser: CborParser
parser.open(s)
parser.next()
parser.nextNode()
proc parseCbor*(s: string): CborNode {.raises: [CborParseError].} =
## Parse a string into a CBOR object.
## A wrapper over stream parsing.
readCbor(newStringStream s)
proc `$`*(n: CborNode): string {.raises: [CborParseError].} =
## Get a ``CborNode`` in diagnostic notation.
result = ""
if n.tag.isSome:
result.add($n.tag.get)
result.add("(")
case n.kind
of cborUnsigned:
result.add $n.uint
of cborNegative:
result.add $n.int
of cborBytes:
result.add "h'"
for c in n.bytes:
result.add(c.toHex)
result.add "'"
of cborText:
result.add escape n.text
of cborArray:
result.add "["
for i in 0 ..< n.seq.high:
result.add $(n.seq[i])
result.add ", "
if n.seq.len > 0:
result.add $(n.seq[n.seq.high])
result.add "]"
of cborMap:
result.add "{"
let final = n.map.len
var i = 1
for k, v in n.map.pairs:
result.add $k
result.add ": "
result.add $v
if i != final:
result.add ", "
inc i
result.add "}"
of cborTag:
discard
of cborSimple:
case n.simple
of 20:
result.add "false"
of 21:
result.add "true"
of 22:
result.add "null"
of 23:
result.add "undefined"
of 31:
discard
# break code for indefinite-length items
else:
result.add "simple(" & $n.simple & ")"
of cborFloat:
case n.float.classify
of fcNan:
result.add "NaN"
of fcInf:
result.add "Infinity"
of fcNegInf:
result.add "-Infinity"
else:
result.add $n.float
of cborRaw:
result.add $parseCbor(n.raw)
if n.tag.isSome:
result.add(")")
proc getInt*(n: CborNode, default: int = 0): int =
## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind
of cborUnsigned: n.uint.int
of cborNegative: n.int.int
else: default
proc parseDateText(n: CborNode): DateTime {.raises: [TimeParseError].} =
parse(n.text, dateTimeFormat)
proc parseTime(n: CborNode): Time =
case n.kind
of cborUnsigned, cborNegative:
result = fromUnix n.getInt
of cborFloat:
result = fromUnixFloat n.float
else:
assert false
proc fromCbor*(_: type DateTime, n: CborNode): ?!DateTime =
## Parse a `DateTime` from the tagged string representation
## defined in RCF7049 section 2.4.1.
var v: DateTime
if n.tag.isSome:
try:
if n.tag.get == 0 and n.kind == cborText:
v = parseDateText(n)
return success(v)
elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}:
v = parseTime(n).utc
return success(v)
except ValueError as e:
return failure(e)
proc fromCbor*(_: type Time, n: CborNode): ?!Time =
## Parse a `Time` from the tagged string representation
## defined in RCF7049 section 2.4.1.
var v: Time
if n.tag.isSome:
try:
if n.tag.get == 0 and n.kind == cborText:
v = parseDateText(n).toTime
return success(v)
elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}:
v = parseTime(n)
return success(v)
except ValueError as e:
return failure(e)
func isTagged*(n: CborNode): bool =
## Check if a CBOR item has a tag.
n.tag.isSome
func hasTag*(n: CborNode, tag: Natural): bool =
## Check if a CBOR item has a tag.
n.tag.isSome and n.tag.get == (uint64) tag
proc `tag=`*(result: var CborNode, tag: Natural) =
## Tag a CBOR item.
result.tag = some(tag.uint64)
func tag*(n: CborNode): uint64 =
## Get a CBOR item tag.
n.tag.get
func isBool*(n: CborNode): bool =
(n.kind == cborSimple) and (n.simple in {20, 21})
func getBool*(n: CborNode, default = false): bool =
## Get the boolean value of a ``CborNode`` or a fallback.
if n.kind == cborSimple:
case n.simple
of 20: false
of 21: true
else: default
else:
default
func isNull*(n: CborNode): bool =
## Return true if ``n`` is a CBOR null.
(n.kind == cborSimple) and (n.simple == 22)
proc getUnsigned*(n: CborNode, default: uint64 = 0): uint64 =
## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind
of cborUnsigned: n.uint
of cborNegative: n.int.uint64
else: default
proc getSigned*(n: CborNode, default: int64 = 0): int64 =
## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind
of cborUnsigned: n.uint.int64
of cborNegative: n.int
else: default
func getFloat*(n: CborNode, default = 0.0): float =
## Get the floating-poing value of a ``CborNode`` or a fallback.
if n.kind == cborFloat: n.float else: default
proc fromCbor*[T: distinct](_: type T, n: CborNode): ?!T =
success T(?T.distinctBase.fromCbor(n))
proc fromCbor*[T: SomeUnsignedInt](_: type T, n: CborNode): ?!T =
expectCborKind(T, {cborUnsigned}, n)
var v = T(n.uint)
if v.BiggestUInt == n.uint:
return success(v)
else:
return failure(newCborError("Value overflow for unsigned integer"))
proc fromCbor*[T: SomeSignedInt](_: type T, n: CborNode): ?!T =
expectCborKind(T, {cborUnsigned, cborNegative}, n)
if n.kind == cborUnsigned:
var v = T(n.uint)
if v.BiggestUInt == n.uint:
return success(v)
else:
return failure(newCborError("Value overflow for signed integer"))
elif n.kind == cborNegative:
var v = T(n.int)
if v.BiggestInt == n.int:
return success(v)
else:
return failure(newCborError("Value overflow for signed integer"))
proc fromCbor*[T: SomeFloat](_: type T, n: CborNode): ?!T =
expectCborKind(T, {cborFloat}, n)
return success(T(n.float))
proc fromCbor*(_: type seq[byte], n: CborNode): ?!seq[byte] =
expectCborKind(seq[byte], cborBytes, n)
return success(n.bytes)
proc fromCbor*(_: type string, n: CborNode): ?!string =
expectCborKind(string, cborText, n)
return success(n.text)
proc fromCbor*(_: type bool, n: CborNode): ?!bool =
if not n.isBool:
return failure(newCborError("Expected boolean, got " & $n.kind))
return success(n.getBool)
proc fromCbor*[T](_: type seq[T], n: CborNode): ?!seq[T] =
expectCborKind(seq[T], cborArray, n)
var arr = newSeq[T](n.seq.len)
for i, elem in n.seq:
arr[i] = ?T.fromCbor(elem)
success arr
proc fromCbor*[T: tuple](_: type T, n: CborNode): ?!T =
expectCborKind(T, cborArray, n)
var res = T.default
if n.seq.len != T.tupleLen:
return failure(newCborError("Expected tuple of length " & $T.tupleLen))
var i: int
for f in fields(res):
f = ?typeof(f).fromCbor(n.seq[i])
inc i
success res
proc fromCbor*[T: ref](_: type T, n: CborNode): ?!T =
when T is ref:
if n.isNull:
return success(T.default)
else:
var resRef = T.new()
let res = typeof(resRef[]).fromCbor(n)
if res.isFailure:
return failure(newCborError(res.error.msg))
resRef[] = res.value
return success(resRef)
proc fromCbor*[T: object](_: type T, n: CborNode): ?!T =
when T is CborNode:
return success T(n)
expectCborKind(T, {cborMap}, n)
var res = T.default
# Added because serde {serialize, deserialize} pragmas and options are not supported for cbor
assertNoPragma(T, deserialize, "deserialize pragma not supported")
try:
var
i: int
key = CborNode(kind: cborText)
for name, value in fieldPairs(
when type(T) is ref:
res[]
else:
res
):
assertNoPragma(value, deserialize, "deserialize pragma not supported")
key.text = name
if not n.map.hasKey key:
return failure(newCborError("Missing field: " & name))
else:
value = ?typeof(value).fromCbor(n.map[key])
inc i
if i == n.map.len:
return success(res)
else:
return failure(newCborError("Extra fields in map"))
except CatchableError as e:
return failure newCborError(e.msg)
except Exception as e:
raise newException(Defect, e.msg, e)
proc fromCbor*[T: ref object or object](_: type T, str: string): ?!T =
var n = ?(parseCbor(str)).catch
T.fromCbor(n)