From dcfec8aaa345c0a5e6c97d08558d1efa1ebb7e6a Mon Sep 17 00:00:00 2001 From: munna0908 Date: Fri, 16 May 2025 18:24:50 +0530 Subject: [PATCH] add cbor types --- serde.nim | 2 + serde/cbor/serializer.nim | 221 ++++++++++++++++++++++++++++++ serde/json.nim | 6 +- serde/json/deserializer.nim | 8 +- serde/json/parser.nim | 4 +- serde/json/serializer.nim | 6 +- serde/utils/cbor.nim | 42 ++++++ serde/{json => utils}/errors.nim | 2 + serde/{json => utils}/pragmas.nim | 0 serde/{json => utils}/stdjson.nim | 2 +- serde/{json => utils}/types.nim | 1 + 11 files changed, 281 insertions(+), 13 deletions(-) create mode 100644 serde/cbor/serializer.nim create mode 100644 serde/utils/cbor.nim rename serde/{json => utils}/errors.nim (96%) rename serde/{json => utils}/pragmas.nim (100%) rename serde/{json => utils}/stdjson.nim (52%) rename serde/{json => utils}/types.nim (94%) diff --git a/serde.nim b/serde.nim index db31316..cb6b1db 100644 --- a/serde.nim +++ b/serde.nim @@ -1,3 +1,5 @@ import ./serde/json +import ./serde/cbor export json +export cbor diff --git a/serde/cbor/serializer.nim b/serde/cbor/serializer.nim new file mode 100644 index 0000000..9b39e60 --- /dev/null +++ b/serde/cbor/serializer.nim @@ -0,0 +1,221 @@ +{.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 diff --git a/serde/json.nim b/serde/json.nim index 407c66c..5d61c62 100644 --- a/serde/json.nim +++ b/serde/json.nim @@ -1,9 +1,9 @@ import ./json/parser import ./json/deserializer -import ./json/stdjson -import ./json/pragmas +import ./utils/stdjson +import ./utils/pragmas import ./json/serializer -import ./json/types +import ./utils/types export parser export deserializer diff --git a/serde/json/deserializer.nim b/serde/json/deserializer.nim index 41c4106..6227e97 100644 --- a/serde/json/deserializer.nim +++ b/serde/json/deserializer.nim @@ -12,10 +12,10 @@ import pkg/questionable import pkg/questionable/results import ./parser -import ./errors -import ./stdjson -import ./pragmas -import ./types +import ../utils/errors +import ../utils/stdjson +import ../utils/pragmas +import ../utils/types import ./helpers export parser diff --git a/serde/json/parser.nim b/serde/json/parser.nim index 5f790e1..c68e79c 100644 --- a/serde/json/parser.nim +++ b/serde/json/parser.nim @@ -2,8 +2,8 @@ import std/json as stdjson import pkg/questionable/results -import ./errors -import ./types +import ../utils/errors +import ../utils/types {.push raises: [].} diff --git a/serde/json/serializer.nim b/serde/json/serializer.nim index 00fdfa2..d405a7d 100644 --- a/serde/json/serializer.nim +++ b/serde/json/serializer.nim @@ -9,9 +9,9 @@ import pkg/questionable import pkg/stew/byteutils import pkg/stint -import ./stdjson -import ./pragmas -import ./types +import ../utils/stdjson +import ../utils/pragmas +import ../utils/types export chronicles except toJson export stdjson diff --git a/serde/utils/cbor.nim b/serde/utils/cbor.nim new file mode 100644 index 0000000..ebe91d1 --- /dev/null +++ b/serde/utils/cbor.nim @@ -0,0 +1,42 @@ +import std/[tables] + +type CborNodeKind* = enum + cborUnsigned = 0, + cborNegative = 1, + cborBytes = 2, + cborText = 3, + cborArray = 4, + cborMap = 5, + cborTag = 6, + cborSimple = 7, + cborFloat, + cborRaw + + CborNode* = object + ## An abstract representation of a CBOR item. Useful for diagnostics. + tag: Option[uint64] + case kind*: CborNodeKind + of cborUnsigned: + uint*: BiggestUInt + of cborNegative: + int*: BiggestInt + of cborBytes: + bytes*: seq[byte] + of cborText: + text*: string + of cborArray: + seq*: seq[CborNode] + of cborMap: + map*: OrderedTable[CborNode, CborNode] + of cborTag: + discard + of cborSimple: + simple*: uint8 + of cborFloat: + float*: float64 + of cborRaw: + raw*: string + +func `==`*(x, y: CborNode): bool + +func hash*(x: CborNode): Hash \ No newline at end of file diff --git a/serde/json/errors.nim b/serde/utils/errors.nim similarity index 96% rename from serde/json/errors.nim rename to serde/utils/errors.nim index 2ee1c0d..4523c09 100644 --- a/serde/json/errors.nim +++ b/serde/utils/errors.nim @@ -13,6 +13,8 @@ proc mapErrTo*[E1: ref CatchableError, E2: SerdeError]( proc newSerdeError*(msg: string): ref SerdeError = newException(SerdeError, msg) +# proc newUnexpectedKindError* + proc newUnexpectedKindError*( expectedType: type, expectedKinds: string, json: JsonNode ): ref UnexpectedKindError = diff --git a/serde/json/pragmas.nim b/serde/utils/pragmas.nim similarity index 100% rename from serde/json/pragmas.nim rename to serde/utils/pragmas.nim diff --git a/serde/json/stdjson.nim b/serde/utils/stdjson.nim similarity index 52% rename from serde/json/stdjson.nim rename to serde/utils/stdjson.nim index 1adde16..9855bd5 100644 --- a/serde/json/stdjson.nim +++ b/serde/utils/stdjson.nim @@ -1,3 +1,3 @@ import std/json except `%`, `%*`, parseJson -export json except `%`, `%*`, parseJson +export json except `%`, `%*`, parseJson \ No newline at end of file diff --git a/serde/json/types.nim b/serde/utils/types.nim similarity index 94% rename from serde/json/types.nim rename to serde/utils/types.nim index facb3b6..c26cd15 100644 --- a/serde/json/types.nim +++ b/serde/utils/types.nim @@ -1,6 +1,7 @@ type SerdeError* = object of CatchableError JsonParseError* = object of SerdeError + CborParseError* = object of SerdeError UnexpectedKindError* = object of SerdeError SerdeMode* = enum OptOut