mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-20 16:29:31 +00:00
109 lines
4.2 KiB
Nim
109 lines
4.2 KiB
Nim
## Wire-format compatibility tests.
|
|
##
|
|
## The C++ side now uses vendored TinyCBOR (see
|
|
## `ffi/codegen/templates/cpp/vendor/tinycbor/`) and the Nim side uses
|
|
## `cbor_serialization`. Both implement RFC 8949, but a regression on either
|
|
## side could silently produce divergent bytes for the same logical value.
|
|
##
|
|
## These tests pin the *exact* byte sequences `cbor_serialization` emits for
|
|
## a handful of representative shapes. If a future bump to the Nim library
|
|
## ever shifts the encoding (e.g., key ordering, integer length choice,
|
|
## optional/null handling), the assertions here will fail loudly before the
|
|
## C++ side gets to discover the divergence at runtime.
|
|
##
|
|
## The same golden bytes are exercised on the C++ side by the timer
|
|
## example's end-to-end round-trip (`examples/timer/cpp_bindings/main.cpp`).
|
|
|
|
import std/[options, strutils]
|
|
import unittest
|
|
import results
|
|
import ffi
|
|
|
|
type WireSimple {.ffi.} = object
|
|
name: string
|
|
|
|
type WireWithInt {.ffi.} = object
|
|
message: string
|
|
delayMs: int
|
|
|
|
type WireWithOption {.ffi.} = object
|
|
label: string
|
|
note: Option[string]
|
|
|
|
type WireWithVector {.ffi.} = object
|
|
items: seq[string]
|
|
|
|
proc toHex(bytes: openArray[byte]): string =
|
|
var buf = ""
|
|
for b in bytes:
|
|
buf.add(b.toHex(2).toLowerAscii())
|
|
return buf
|
|
|
|
suite "wire format — single-field map":
|
|
test "WireSimple{name:\"abc\"} round-trips to a stable byte sequence":
|
|
let v = WireSimple(name: "abc")
|
|
let bytes = cborEncode(v)
|
|
# map(1), key "name" (text-string len 4), value "abc" (text-string len 3)
|
|
check toHex(bytes) == "a1646e616d6563616263"
|
|
let back = cborDecode(bytes, WireSimple)
|
|
check back.isOk
|
|
check back.value.name == "abc"
|
|
|
|
suite "wire format — int field":
|
|
test "WireWithInt encodes ints as CBOR integers":
|
|
let v = WireWithInt(message: "hi", delayMs: 200)
|
|
let bytes = cborEncode(v)
|
|
# map(2), "message"->"hi", "delayMs"->200 (uint8 form: 0x18 0xc8)
|
|
check toHex(bytes) == "a2676d65737361676562686967" & "64656c61794d7318c8"
|
|
let back = cborDecode(bytes, WireWithInt)
|
|
check back.isOk
|
|
check back.value.message == "hi"
|
|
check back.value.delayMs == 200
|
|
|
|
test "negative int uses CBOR negative-integer major type":
|
|
let v = WireWithInt(message: "x", delayMs: -1)
|
|
let bytes = cborEncode(v)
|
|
# 0x20 is CBOR -1
|
|
check toHex(bytes).endsWith("20")
|
|
|
|
suite "wire format — Option[T]":
|
|
## Nim's `cbor_serialization/std/options` import encodes `Option[T]`:
|
|
## - `some v` → emit the key and the inner value.
|
|
## - `none T` → **omit the field entirely** from the map (the resulting
|
|
## map is smaller by one entry).
|
|
##
|
|
## The C++ TinyCBOR helper currently encodes `std::nullopt` as CBOR null
|
|
## (0xf6). That divergence is invisible while no consumer sends
|
|
## `std::nullopt` over the wire (the timer example only sends `Some`
|
|
## values). If a future caller does, we'll need to align the conventions
|
|
## — either teach the C++ codec to skip None-valued keys (mirroring Nim),
|
|
## or switch the Nim side to emit explicit nulls. This test pins the
|
|
## current Nim behavior so the divergence is detectable instead of
|
|
## silent.
|
|
|
|
test "Option.some encodes as the inner value (no wrapper)":
|
|
let v = WireWithOption(label: "x", note: some("hi"))
|
|
let bytes = cborEncode(v)
|
|
# map(2): "label"->"x", "note"->"hi" (text strings, no null/tag wrapping)
|
|
check toHex(bytes) == "a2656c6162656c6178646e6f7465626869"
|
|
|
|
test "Option.none yields a smaller map without the optional key":
|
|
let v = WireWithOption(label: "x", note: none(string))
|
|
let bytes = cborEncode(v)
|
|
# map(1): only "label"->"x"; the "note" key is absent.
|
|
check toHex(bytes) == "a1656c6162656c6178"
|
|
|
|
suite "wire format — seq[T]":
|
|
test "empty seq encodes as CBOR array(0)":
|
|
let v = WireWithVector(items: @[])
|
|
let bytes = cborEncode(v)
|
|
# a1 (map 1) 65 (text-str len 5) 69 74 65 6d 73 ("items") 80 (array 0)
|
|
check toHex(bytes) == "a1656974656d7380"
|
|
|
|
test "three-element seq[string]":
|
|
let v = WireWithVector(items: @["a", "bb", "ccc"])
|
|
let bytes = cborEncode(v)
|
|
# map(1), "items" -> array(3) of text strings "a", "bb", "ccc":
|
|
# 83 (array 3) 61 61 ("a") 62 62 62 ("bb") 63 63 63 63 ("ccc")
|
|
check toHex(bytes) == "a1656974656d7383616162626263636363"
|