nim-serde/serde/cbor/serializer.nim
2025-05-16 18:24:50 +05:30

222 lines
6.5 KiB
Nim

{.push checks: off.}
import ../utils/cbor
func initialByte(major, minor: Natural): uint8 {.inline.} =
uint8((major shl 5) or (minor and 0b11111))
proc writeInitial[T: SomeInteger](str: Stream; m: uint8; n: T) =
## Write the initial integer of a CBOR item.
let m = m shl 5
when T is byte:
if n < 24:
str.write(m or n.uint8)
else:
str.write(m or 24'u8)
str.write(n)
else:
if n < 24:
str.write(m or n.uint8)
elif uint64(n) <= uint64(uint8.high):
str.write(m or 24'u8)
str.write(n.uint8)
elif uint64(n) <= uint64(uint16.high):
str.write(m or 25'u8)
str.write((uint8)n shr 8)
str.write((uint8)n)
elif uint64(n) <= uint64(uint32.high):
str.write(m or 26'u8)
for i in countdown(24, 8, 8):
{.unroll.}
str.write((uint8)n shr i)
str.write((uint8)n)
else:
str.write(m or 27'u8)
for i in countdown(56, 8, 8):
{.unroll.}
str.write((uint8)n shr i)
str.write((uint8)n)
{.pop.}
proc writeCborArrayLen*(str: Stream; len: Natural) =
## Write a marker to the stream that initiates an array of ``len`` items.
str.writeInitial(4, len)
proc writeCborIndefiniteArrayLen*(str: Stream) =
## Write a marker to the stream that initiates an array of indefinite length.
## Indefinite length arrays are composed of an indefinite amount of arrays
## of definite lengths.
str.write(initialByte(4, 31))
proc writeCborMapLen*(str: Stream; len: Natural) =
## Write a marker to the stream that initiates an map of ``len`` pairs.
str.writeInitial(5, len)
proc writeCborIndefiniteMapLen*(str: Stream) =
## Write a marker to the stream that initiates a map of indefinite length.
## Indefinite length maps are composed of an indefinite amount of maps
## of definite length.
str.write(initialByte(5, 31))
proc writeCborBreak*(str: Stream) =
## Write a marker to the stream that ends an indefinite array or map.
str.write(initialByte(7, 31))
proc writeCborTag*(str: Stream; tag: Natural) {.inline.} =
## Write a tag for the next CBOR item to a binary stream.
str.writeInitial(6, tag)
proc writeCbor*(str: Stream; buf: pointer; len: int) =
## Write a raw buffer to a CBOR `Stream`.
str.writeInitial(BytesMajor, len)
if len > 0: str.writeData(buf, len)
proc writeCbor*[T](str: Stream; v: T) =
## Write the CBOR binary representation of a `T` to a `Stream`.
## The behavior of this procedure can be extended or overriden
## by defining `proc writeCborHook(str: Stream; v: T)` for specific
## types `T`.
when T is CborNode:
if v.tag.isSome:
str.writeCborTag(v.tag.get)
case v.kind:
of cborUnsigned:
str.writeCbor(v.uint)
of cborNegative:
str.writeCbor(v.int)
of cborBytes:
str.writeInitial(cborBytes.uint8, v.bytes.len)
for b in v.bytes.items:
str.write(b)
of cborText:
str.writeInitial(cborText.uint8, v.text.len)
str.write(v.text)
of cborArray:
str.writeInitial(4, v.seq.len)
for e in v.seq:
str.writeCbor(e)
of cborMap:
assert(v.isSorted, "refusing to write unsorted map to stream")
str.writeInitial(5, v.map.len)
for k, f in v.map.pairs:
str.writeCbor(k)
str.writeCbor(f)
of cborTag:
discard
of cborSimple:
if v.simple > 31'u or v.simple == 24:
str.write(initialByte(cborSimple.uint8, 24))
str.write(v.simple)
else:
str.write(initialByte(cborSimple.uint8, v.simple))
of cborFloat:
str.writeCbor(v.float)
of cborRaw:
str.write(v.raw)
elif compiles(writeCborHook(str, v)):
writeCborHook(str, v)
elif T is SomeUnsignedInt:
str.writeInitial(0, v)
elif T is SomeSignedInt:
if v < 0:
# Major type 1
str.writeInitial(1, -1 - v)
else:
# Major type 0
str.writeInitial(0, v)
elif T is seq[byte]:
str.writeInitial(BytesMajor, v.len)
if v.len > 0:
str.writeData(unsafeAddr v[0], v.len)
elif T is openArray[char | uint8 | int8]:
str.writeInitial(BytesMajor, v.len)
if v.len > 0:
str.writeData(unsafeAddr v[0], v.len)
elif T is string:
str.writeInitial(TextMajor, v.len)
str.write(v)
elif T is array | seq:
str.writeInitial(4, v.len)
for e in v.items:
writeCbor(str, e)
elif T is tuple:
str.writeInitial(4, T.tupleLen)
for f in v.fields: str.writeCbor(f)
elif T is ptr | ref:
if system.`==`(v, nil):
# Major type 7
str.write(Null)
else: writeCbor(str, v[])
elif T is object:
var n: uint
for _, _ in v.fieldPairs:
inc n
str.writeInitial(5, n)
for k, f in v.fieldPairs:
str.writeCbor(k)
str.writeCbor(f)
elif T is bool:
str.write(initialByte(7, (if v: 21 else: 20)))
elif T is SomeFloat:
case v.classify
of fcNormal, fcSubnormal:
let single = v.float32
if single.float64 == v.float64:
if single.isHalfPrecise:
let half = floatHalf(single)
str.write(initialByte(7, 25))
when system.cpuEndian == bigEndian:
str.write(half)
else:
var be: uint16
swapEndian16 be.addr, half.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 26)
when system.cpuEndian == bigEndian:
str.write(single)
else:
var be: uint32
swapEndian32 be.addr, single.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 27)
when system.cpuEndian == bigEndian:
str.write(v)
else:
var be: float64
swapEndian64 be.addr, v.unsafeAddr
str.write be
return
of fcZero:
str.write initialByte(7, 25)
str.write((char)0x00)
of fcNegZero:
str.write initialByte(7, 25)
str.write((char)0x80)
of fcInf:
str.write initialByte(7, 25)
str.write((char)0x7c)
of fcNan:
str.write initialByte(7, 25)
str.write((char)0x7e)
of fcNegInf:
str.write initialByte(7, 25)
str.write((char)0xfc)
str.write((char)0x00)
proc writeCborArray*(str: Stream; args: varargs[CborNode, toCbor]) =
## Encode to a CBOR array in binary form. This magic doesn't
## always work, some arguments may need to be explicitly
## converted with ``toCbor`` before passing.
str.writeCborArrayLen(args.len)
for x in args:
str.writeCbor(x)
proc encode*[T](v: T): string =
## Encode an arbitrary value to CBOR binary representation.
## A wrapper over ``writeCbor``.
let s = newStringStream()
s.writeCbor(v)
s.data