Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

@@ -0,0 +1,636 @@
[
{
"cbor": "AA==",
"hex": "00",
"roundtrip": true,
"decoded": 0
},
{
"cbor": "AQ==",
"hex": "01",
"roundtrip": true,
"decoded": 1
},
{
"cbor": "Cg==",
"hex": "0a",
"roundtrip": true,
"decoded": 10
},
{
"cbor": "Fw==",
"hex": "17",
"roundtrip": true,
"decoded": 23
},
{
"cbor": "GBg=",
"hex": "1818",
"roundtrip": true,
"decoded": 24
},
{
"cbor": "GBk=",
"hex": "1819",
"roundtrip": true,
"decoded": 25
},
{
"cbor": "GGQ=",
"hex": "1864",
"roundtrip": true,
"decoded": 100
},
{
"cbor": "GQPo",
"hex": "1903e8",
"roundtrip": true,
"decoded": 1000
},
{
"cbor": "GgAPQkA=",
"hex": "1a000f4240",
"roundtrip": true,
"decoded": 1000000
},
{
"cbor": "GwAAAOjUpRAA",
"hex": "1b000000e8d4a51000",
"roundtrip": true,
"decoded": 1000000000000
},
{
"cbor": "G///////////",
"hex": "1bffffffffffffffff",
"roundtrip": true,
"decoded": 18446744073709551615
},
{
"cbor": "wkkBAAAAAAAAAAA=",
"hex": "c249010000000000000000",
"roundtrip": true,
"decoded": 18446744073709551616
},
{
"cbor": "O///////////",
"hex": "3bffffffffffffffff",
"roundtrip": true,
"decoded": -18446744073709551616
},
{
"cbor": "w0kBAAAAAAAAAAA=",
"hex": "c349010000000000000000",
"roundtrip": true,
"decoded": -18446744073709551617
},
{
"cbor": "IA==",
"hex": "20",
"roundtrip": true,
"decoded": -1
},
{
"cbor": "KQ==",
"hex": "29",
"roundtrip": true,
"decoded": -10
},
{
"cbor": "OGM=",
"hex": "3863",
"roundtrip": true,
"decoded": -100
},
{
"cbor": "OQPn",
"hex": "3903e7",
"roundtrip": true,
"decoded": -1000
},
{
"cbor": "+QAA",
"hex": "f90000",
"roundtrip": true,
"decoded": 0.0
},
{
"cbor": "+YAA",
"hex": "f98000",
"roundtrip": true,
"decoded": -0.0
},
{
"cbor": "+TwA",
"hex": "f93c00",
"roundtrip": true,
"decoded": 1.0
},
{
"cbor": "+z/xmZmZmZma",
"hex": "fb3ff199999999999a",
"roundtrip": true,
"decoded": 1.1
},
{
"cbor": "+T4A",
"hex": "f93e00",
"roundtrip": true,
"decoded": 1.5
},
{
"cbor": "+Xv/",
"hex": "f97bff",
"roundtrip": true,
"decoded": 65504.0
},
{
"cbor": "+kfDUAA=",
"hex": "fa47c35000",
"roundtrip": true,
"decoded": 100000.0
},
{
"cbor": "+n9///8=",
"hex": "fa7f7fffff",
"roundtrip": true,
"decoded": 3.4028234663852886e+38
},
{
"cbor": "+3435DyIAHWc",
"hex": "fb7e37e43c8800759c",
"roundtrip": true,
"decoded": 1.0e+300
},
{
"cbor": "+QAB",
"hex": "f90001",
"roundtrip": true,
"decoded": 5.960464477539063e-08
},
{
"cbor": "+QQA",
"hex": "f90400",
"roundtrip": true,
"decoded": 6.103515625e-05
},
{
"cbor": "+cQA",
"hex": "f9c400",
"roundtrip": true,
"decoded": -4.0
},
{
"cbor": "+8AQZmZmZmZm",
"hex": "fbc010666666666666",
"roundtrip": true,
"decoded": -4.1
},
{
"cbor": "+XwA",
"hex": "f97c00",
"roundtrip": true,
"diagnostic": "Infinity"
},
{
"cbor": "+X4A",
"hex": "f97e00",
"roundtrip": true,
"diagnostic": "NaN"
},
{
"cbor": "+fwA",
"hex": "f9fc00",
"roundtrip": true,
"diagnostic": "-Infinity"
},
{
"cbor": "+n+AAAA=",
"hex": "fa7f800000",
"roundtrip": false,
"diagnostic": "Infinity"
},
{
"cbor": "+n/AAAA=",
"hex": "fa7fc00000",
"roundtrip": false,
"diagnostic": "NaN"
},
{
"cbor": "+v+AAAA=",
"hex": "faff800000",
"roundtrip": false,
"diagnostic": "-Infinity"
},
{
"cbor": "+3/wAAAAAAAA",
"hex": "fb7ff0000000000000",
"roundtrip": false,
"diagnostic": "Infinity"
},
{
"cbor": "+3/4AAAAAAAA",
"hex": "fb7ff8000000000000",
"roundtrip": false,
"diagnostic": "NaN"
},
{
"cbor": "+//wAAAAAAAA",
"hex": "fbfff0000000000000",
"roundtrip": false,
"diagnostic": "-Infinity"
},
{
"cbor": "9A==",
"hex": "f4",
"roundtrip": true,
"decoded": false
},
{
"cbor": "9Q==",
"hex": "f5",
"roundtrip": true,
"decoded": true
},
{
"cbor": "9g==",
"hex": "f6",
"roundtrip": true,
"decoded": null
},
{
"cbor": "9w==",
"hex": "f7",
"roundtrip": true,
"diagnostic": "undefined"
},
{
"cbor": "8A==",
"hex": "f0",
"roundtrip": true,
"diagnostic": "simple(16)"
},
{
"cbor": "+Bg=",
"hex": "f818",
"roundtrip": true,
"diagnostic": "simple(24)"
},
{
"cbor": "+P8=",
"hex": "f8ff",
"roundtrip": true,
"diagnostic": "simple(255)"
},
{
"cbor": "wHQyMDEzLTAzLTIxVDIwOjA0OjAwWg==",
"hex": "c074323031332d30332d32315432303a30343a30305a",
"roundtrip": true,
"diagnostic": "0(\"2013-03-21T20:04:00Z\")"
},
{
"cbor": "wRpRS2ew",
"hex": "c11a514b67b0",
"roundtrip": true,
"diagnostic": "1(1363896240)"
},
{
"cbor": "wftB1FLZ7CAAAA==",
"hex": "c1fb41d452d9ec200000",
"roundtrip": true,
"diagnostic": "1(1363896240.5)"
},
{
"cbor": "10QBAgME",
"hex": "d74401020304",
"roundtrip": true,
"diagnostic": "23(h'01020304')"
},
{
"cbor": "2BhFZElFVEY=",
"hex": "d818456449455446",
"roundtrip": true,
"diagnostic": "24(h'6449455446')"
},
{
"cbor": "2CB2aHR0cDovL3d3dy5leGFtcGxlLmNvbQ==",
"hex": "d82076687474703a2f2f7777772e6578616d706c652e636f6d",
"roundtrip": true,
"diagnostic": "32(\"http://www.example.com\")"
},
{
"cbor": "QA==",
"hex": "40",
"roundtrip": true,
"diagnostic": "h''"
},
{
"cbor": "RAECAwQ=",
"hex": "4401020304",
"roundtrip": true,
"diagnostic": "h'01020304'"
},
{
"cbor": "YA==",
"hex": "60",
"roundtrip": true,
"decoded": ""
},
{
"cbor": "YWE=",
"hex": "6161",
"roundtrip": true,
"decoded": "a"
},
{
"cbor": "ZElFVEY=",
"hex": "6449455446",
"roundtrip": true,
"decoded": "IETF"
},
{
"cbor": "YiJc",
"hex": "62225c",
"roundtrip": true,
"decoded": "\"\\"
},
{
"cbor": "YsO8",
"hex": "62c3bc",
"roundtrip": true,
"decoded": "ü"
},
{
"cbor": "Y+awtA==",
"hex": "63e6b0b4",
"roundtrip": true,
"decoded": "水"
},
{
"cbor": "ZPCQhZE=",
"hex": "64f0908591",
"roundtrip": true,
"decoded": "𐅑"
},
{
"cbor": "gA==",
"hex": "80",
"roundtrip": true,
"decoded": [
]
},
{
"cbor": "gwECAw==",
"hex": "83010203",
"roundtrip": true,
"decoded": [
1,
2,
3
]
},
{
"cbor": "gwGCAgOCBAU=",
"hex": "8301820203820405",
"roundtrip": true,
"decoded": [
1,
[
2,
3
],
[
4,
5
]
]
},
{
"cbor": "mBkBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgYGBk=",
"hex": "98190102030405060708090a0b0c0d0e0f101112131415161718181819",
"roundtrip": true,
"decoded": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
"cbor": "oA==",
"hex": "a0",
"roundtrip": true,
"decoded": {
}
},
{
"cbor": "ogECAwQ=",
"hex": "a201020304",
"roundtrip": true,
"diagnostic": "{1: 2, 3: 4}"
},
{
"cbor": "omFhAWFiggID",
"hex": "a26161016162820203",
"roundtrip": true,
"decoded": {
"a": 1,
"b": [
2,
3
]
}
},
{
"cbor": "gmFhoWFiYWM=",
"hex": "826161a161626163",
"roundtrip": true,
"decoded": [
"a",
{
"b": "c"
}
]
},
{
"cbor": "pWFhYUFhYmFCYWNhQ2FkYURhZWFF",
"hex": "a56161614161626142616361436164614461656145",
"roundtrip": true,
"decoded": {
"a": "A",
"b": "B",
"c": "C",
"d": "D",
"e": "E"
}
},
{
"cbor": "X0IBAkMDBAX/",
"hex": "5f42010243030405ff",
"roundtrip": false,
"diagnostic": "(_ h'0102', h'030405')"
},
{
"cbor": "f2VzdHJlYWRtaW5n/w==",
"hex": "7f657374726561646d696e67ff",
"roundtrip": false,
"decoded": "streaming"
},
{
"cbor": "n/8=",
"hex": "9fff",
"roundtrip": false,
"decoded": [
]
},
{
"cbor": "nwGCAgOfBAX//w==",
"hex": "9f018202039f0405ffff",
"roundtrip": false,
"decoded": [
1,
[
2,
3
],
[
4,
5
]
]
},
{
"cbor": "nwGCAgOCBAX/",
"hex": "9f01820203820405ff",
"roundtrip": false,
"decoded": [
1,
[
2,
3
],
[
4,
5
]
]
},
{
"cbor": "gwGCAgOfBAX/",
"hex": "83018202039f0405ff",
"roundtrip": false,
"decoded": [
1,
[
2,
3
],
[
4,
5
]
]
},
{
"cbor": "gwGfAgP/ggQF",
"hex": "83019f0203ff820405",
"roundtrip": false,
"decoded": [
1,
[
2,
3
],
[
4,
5
]
]
},
{
"cbor": "nwECAwQFBgcICQoLDA0ODxAREhMUFRYXGBgYGf8=",
"hex": "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff",
"roundtrip": false,
"decoded": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25
]
},
{
"cbor": "v2FhAWFinwID//8=",
"hex": "bf61610161629f0203ffff",
"roundtrip": false,
"decoded": {
"a": 1,
"b": [
2,
3
]
}
},
{
"cbor": "gmFhv2FiYWP/",
"hex": "826161bf61626163ff",
"roundtrip": false,
"decoded": [
"a",
{
"b": "c"
}
]
},
{
"cbor": "v2NGdW71Y0FtdCH/",
"hex": "bf6346756ef563416d7421ff",
"roundtrip": false,
"decoded": {
"Fun": true,
"Amt": -2
}
}
]
@@ -0,0 +1,148 @@
// Canonical encoding (RFC 8949 §4.2.1, "core deterministic encoding"):
// map keys are sorted by length-first lexicographic order of their
// encoded forms. Used for hashable / signable payloads.
module main
import encoding.cbor
import encoding.hex
fn h(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
fn beq(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}
fn test_canonical_sorts_text_keys() {
// Build a map with reverse-alphabetic insertion order; canonical
// output should still emit keys "a", "b", "c", "d", "e".
v := cbor.Value(cbor.Map{
pairs: [
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'e'
})
value: cbor.Value(cbor.Text{
value: 'E'
})
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'b'
})
value: cbor.Value(cbor.Text{
value: 'B'
})
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'd'
})
value: cbor.Value(cbor.Text{
value: 'D'
})
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'a'
})
value: cbor.Value(cbor.Text{
value: 'A'
})
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'c'
})
value: cbor.Value(cbor.Text{
value: 'C'
})
},
]
})
got := cbor.encode_value(v, cbor.EncodeOpts{ canonical: true })!
want := h('a56161614161626142616361436164614461656145')
assert beq(got, want), 'canonical: got ${hex.encode(got)}, want ${hex.encode(want)}'
}
fn test_canonical_length_first_then_lex() {
// Length-first ordering: shorter keys first.
// {"a": 1, "aa": 2} → short before long.
v := cbor.Value(cbor.Map{
pairs: [
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'aa'
})
value: cbor.Value(cbor.new_uint(2))
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'a'
})
value: cbor.Value(cbor.new_uint(1))
},
]
})
got := cbor.encode_value(v, cbor.EncodeOpts{ canonical: true })!
// Encoded keys "a"=0x6161 (2 bytes), "aa"=0x626161 (3 bytes).
// Length-first: "a" first, then "aa".
// Result: a2 61 61 01 62 61 61 02
want := h('a2616101626161 02'.replace(' ', ''))
assert beq(got, want), 'length-first: got ${hex.encode(got)}'
}
fn test_self_describe_prefix() {
bytes := cbor.encode[u64](u64(0), cbor.EncodeOpts{ self_describe: true })!
// Magic prefix: d9 d9 f7 then 0x00.
assert beq(bytes, [u8(0xd9), 0xd9, 0xf7, 0x00])
}
// ---------------------------------------------------------------------
// Struct-as-map canonical encoding: declaration order MUST NOT leak
// into the wire form when canonical mode is on. Otherwise hash- or
// signature-based payloads (COSE, CWT, DAG-CBOR) lose stability across
// V versions whenever a field is added or reordered in source.
// ---------------------------------------------------------------------
struct OutOfOrder {
zeta int
alpha int
mid int
}
fn test_canonical_struct_sorts_keys_by_encoded_form() {
v := OutOfOrder{
zeta: 1
alpha: 2
mid: 3
}
got := cbor.encode[OutOfOrder](v, cbor.EncodeOpts{ canonical: true })!
// Length-first lex on encoded keys: "mid" (4B) < "zeta" (5B) < "alpha" (6B).
// a3 636d6964 03 647a657461 01 65616c706861 02
want := h('a3636d69640364 7a65746101 65616c706861 02'.replace(' ', ''))
assert beq(got, want), 'declaration order leaked: got ${hex.encode(got)}'
}
fn test_canonical_struct_preserves_declaration_order_when_off() {
// Default (non-canonical) keeps source order — important for human
// inspection and matches the documented permissive behaviour.
v := OutOfOrder{
zeta: 1
alpha: 2
mid: 3
}
got := cbor.encode[OutOfOrder](v, cbor.EncodeOpts{})!
// a3 647a657461 01 65616c706861 02 636d6964 03
want := h('a3647a65746101 65616c706861 02 636d6964 03'.replace(' ', ''))
assert beq(got, want), 'non-canonical reorder: got ${hex.encode(got)}'
}
@@ -0,0 +1,61 @@
{
"title": "mt0",
"description": "Plain CBOR integers storable as major type 0 (mt0), from RFC 8949 appendix A",
"tests": [
{
"description": "mt0 zero",
"encoded": h'00',
"decoded": 0,
},
{
"description": "mt0 one",
"encoded": h'01',
"decoded": 1,
},
{
"description": "mt0 ten, not newline",
"encoded": h'0a',
"decoded": 10,
},
{
"description": "mt0 largest 0-byte",
"encoded": h'17',
"decoded": 23,
},
{
"description": "mt0 smallest 1-byte",
"encoded": h'1818',
"decoded": 24,
},
{
"description": "mt0 second 1-byte",
"encoded": h'1819',
"decoded": 25,
},
{
"description": "mt0 100",
"encoded": h'1864',
"decoded": 100,
},
{
"description": "mt0 1000",
"encoded": h'1903e8',
"decoded": 1000,
},
{
"description": "mt0 1000000",
"encoded": h'1a000f4240',
"decoded": 1000000,
},
{
"description": "mt0 1000000000000",
"encoded": h'1b000000e8d4a51000',
"decoded": 1000000000000,
},
{
"description": "mt0 largest",
"encoded": h'1bffffffffffffffff',
"decoded": 18446744073709551615,
},
]
}
@@ -0,0 +1,31 @@
{
"title": "mt1",
"description": "Plain negative CBOR integers storable as major type 1 (mt1), from RFC 8949 appendix A",
"tests": [
{
"description": "mt1 minimum",
"encoded": h'3bffffffffffffffff',
"decoded": -18446744073709551616,
},
{
"description": "mt1 maximum",
"encoded": h'20',
"decoded": -1,
},
{
"description": "mt1 -10",
"encoded": h'29',
"decoded": -10,
}
{
"description": "mt1 -100",
"encoded": h'3863',
"decoded": -100,
}
{
"description": "mt1 -1000",
"encoded": h'3903e7',
"decoded": -1000,
}
],
}
@@ -0,0 +1,16 @@
{
"title": "mt2",
"description": "Byte strings, from RFC 8949 appendix A",
"tests": [
{
"description": "empty",
"encoded": h'40',
"decoded": h'',
},
{
"description": "four bytes in a byte string",
"encoded": h'4401020304',
"decoded": h'01020304',
},
],
}
@@ -0,0 +1,41 @@
{
"title": "mt3",
"description": "UTF8 strings, from RFC 8949 appendix A",
"tests": [
{
"description": "Empty string",
"encoded": h'60',
"decoded": "",
},
{
"description": "Short string",
"encoded": h'6161',
"decoded": "a",
},
{
"description": "Important string",
"encoded": h'6449455446',
"decoded": "IETF",
},
{
"description": "Escaped string",
"encoded": h'62225c',
"decoded": "\"\\",
},
{
"description": "String with short Unicode escape",
"encoded": h'62c3bc',
"decoded": "\u00fc",
},
{
"description": "String with long Unicode escape",
"encoded": h'63e6b0b4',
"decoded": "\u6c34",
},
{
"description": "String with UTF-16 Unicode escapes",
"encoded": h'64f0908591',
"decoded": "\ud800\udd51",
},
],
}
@@ -0,0 +1,26 @@
{
"title": "mt4",
"description": "Arrays, from RFC 8949 appendix A",
"tests": [
{
"description": "Empty Array",
"encoded": h'80',
"decoded": [],
},
{
"description": "Short Array",
"encoded": h'83010203',
"decoded": [1, 2, 3],
},
{
"description": "Nested Arrays",
"encoded": h'8301820203820405',
"decoded": [1, [2, 3], [4, 5]],
},
{
"description": "Longer array",
"encoded": h'98190102030405060708090a0b0c0d0e0f101112131415161718181819',
"decoded": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
},
]
}
@@ -0,0 +1,31 @@
{
"title": "mt5",
"description": "Maps, from RFC 8949 appendix A",
"tests": [
{
"description": "Empty Map",
"encoded": h'a0',
"decoded": {},
},
{
"description": "Map with integer keys",
"encoded": h'a201020304',
"decoded": {1: 2, 3: 4},
},
{
"description": "Array nested in Map with string keys",
"encoded": h'a26161016162820203',
"decoded": {"a": 1, "b": [2, 3]},
},
{
"description": "Map nested in array",
"encoded": h'826161a161626163',
"decoded": ["a", {"b": "c"}],
},
{
"description": "Map with more keys",
"encoded": h'a56161614161626142616361436164614461656145',
"decoded": {"a": "A", "b": "B", "c": "C", "d": "D", "e": "E"},
},
],
}
@@ -0,0 +1,51 @@
{
"title": "mt6",
"description": "Tags, from RFC 8949 appendix A",
"tests": [
{
"description": "Bigint"
"encoded": h'c249010000000000000000',
"decoded": 18446744073709551616,
},
{
"description": "Negative Bigint"
"encoded": h'c349010000000000000000',
"decoded": -18446744073709551617,
},
{
"description": "RFC3339 Date/Time String"
"encoded": h'c074323031332d30332d32315432303a30343a30305a',
"decoded": 0("2013-03-21T20:04:00Z"),
"encodeOptions": {
"dateTag": 0,
},
},
{
"description": "Date/time as integer offset from epoch",
"encoded": h'c11a514b67b0',
"decoded": 1(1363896240),
},
{
"description": "Date/time as float offset from epoch",
"encoded": h'c1fb41d452d9ec200000',
"decoded": 1(1363896240.5),
},
{
"description": "Expected conversion to base16",
"encoded": h'd74401020304',
"decoded": 23(h'01020304')
},
{
"description": "Expected conversion to base16 encoding",
"encoded": h'd818456449455446',
"decoded": 24(h'6449455446'),
},
{
# Note: RFC 8949 doesn't include trailing slash, which should probably
# be there.
"description": "URL",
"encoded": h'd82077687474703a2f2f7777772e6578616d706c652e636f6d2f'
"decoded": 32("http://www.example.com/"),
},
],
}
@@ -0,0 +1,131 @@
{
"title": "mt7-float",
"description": "Major type 7 (mt7) floats, from RFC 8949 appendix A",
"encodeOptions": {
"avoidInts": true,
},
"tests": [
{
"description": "Zero",
"encoded": h'f90000',
"decoded": 0.0,
},
{
"description": "Negative zero",
"encoded": h'f98000',
"decoded": -0.0,
},
{
"description": "One",
"encoded": h'f93c00',
"decoded": 1.0,
},
{
"description": "1.1 does not fit into a smaller float",
"encoded": h'fb3ff199999999999a',
"decoded": 1.1,
},
{
"description": "1.5 collapses to an f16"
"encoded": h'f93e00',
"decoded": 1.5,
},
{
"description": "Largest f16"
"encoded": h'f97bff',
"decoded": 65504.0,
},
{
"description": "100000"
"encoded": h'fa47c35000',
"decoded": 100000.0,
},
{
"description": "Largest f32"
"encoded": h'fa7f7fffff',
"decoded": 3.4028234663852886e+38,
},
{
"description": "Large round f64"
"encoded": h'fb7e37e43c8800759c',
"decoded": 1.0e+300,
},
{
"description": "Smallest subnormal f16"
"encoded": h'f90001',
"decoded": 5.960464477539063e-8,
},
{
"description": "Smallest normal f16"
"encoded": h'f90400',
"decoded": 0.00006103515625,
},
{
"description": "Negative f16"
"encoded": h'f9c400',
"decoded": -4.0,
},
{
"description": "-4.1 does not reduce"
"encoded": h'fbc010666666666666',
"decoded": -4.1,
},
{
"description": "Positive infinity"
"encoded": h'f97c00',
"decoded": Infinity,
},
{
"description": "Trivial NaN"
"encoded": h'f97e00',
"decoded": NaN,
},
{
"description": "Negative infinity"
"encoded": h'f9fc00',
"decoded": -Infinity,
},
{
"description": "Infinity coded as f32 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'fa7f800000',
"decoded": Infinity,
},
{
"description": "Trivial NaN coded as f32 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'fa7fc00000',
"decoded": NaN,
},
{
"description": "Negative Infinity coded as f32 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'faff800000',
"decoded": -Infinity,
},
{
"description": "Infinity coded as f64 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'fb7ff0000000000000',
"decoded": Infinity,
},
{
"description": "Trivial NaN coded as f64 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'fb7ff8000000000000',
"decoded": NaN,
},
{
"description": "Negative Infinity coded as f64 instead of f16",
# Can't round-trip to non-preferred
"roundtrip": false,
"encoded": h'fbfff0000000000000',
"decoded": -Infinity,
},
],
}
@@ -0,0 +1,36 @@
{
"title": "mt6-simple",
"description": "Major type 7 (mt7) simple values, from RFC 8949 appendix A",
"tests": [
{
"description": "false",
"encoded": h'f4',
"decoded": false
},
{
"description": "true",
"encoded": h'f5',
"decoded": true
},
{
"description": "null",
"encoded": h'f6',
"decoded": null
},
{
"description": "undefined",
"encoded": h'f7',
"decoded": undefined
},
{
"description": "Unknown 0-byte simple value",
"encoded": h'f0',
"decoded": simple(16),
},
{
"description": "Unknown 1-byte simple value",
"encoded": h'f8ff',
"decoded": simple(255),
},
],
}
@@ -0,0 +1,72 @@
{
"title": "streaming",
"description": "Streaming, from RFC 8949 appendix A",
"tests": [
{
"description": "Streamed byte string",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'5f42010243030405ff',
"decoded": (_ h'0102', h'030405'),
},
{
"description": "Streamed UTF8 string",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'7f657374726561646d696e67ff',
"decoded": (_ "strea", "ming"),
},
{
"description": "Streamed array",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'9fff',
"decoded": [_ ],
},
{
"description": "Streamed array, nested non-streamed then streamed arrays",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'9f018202039f0405ffff',
"decoded": [_ 1, [2, 3], [_ 4, 5]],
},
{
"description": "Streamed array, nested non-streamed arrays",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'9f01820203820405ff',
"decoded": [_ 1, [2, 3], [4, 5]],
},
{
"description": "Non-Streamed array, nested non-streamed then streamed array",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'83018202039f0405ff',
"decoded": [1, [2, 3], [_ 4, 5]],
},
{
"description": "Non-Streamed array, nested streamed then non-streamed array",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'83019f0203ff820405',
"decoded": [1, [_ 2, 3], [4, 5]],
},
{
"description": "Longer streamed array of ints",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff',
"decoded": [_ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
},
{
"description": "Streamed map",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'bf61610161629f0203ffff',
"decoded": {_ "a": 1, "b": [_ 2, 3]},
},
{
"description": "Array with nested streamed map",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'826161bf61626163ff',
"decoded": ["a", {_ "b": "c"}],
},
{
"description": "Plain streamed map",
"roundtrip": false, # decoded version is de-streamed.
"encoded": h'bf6346756ef563416d7421ff',
"decoded": {_ "Fun": true, "Amt": -2},
},
],
}
@@ -0,0 +1,197 @@
{
"title": "bad",
"description": "Inputs that should fail for RFC 8949",
"fail": true,
"tests": [
{
"description": "Missing the next byte for mt0 ai 24",
"encoded": h'18',
},
{
"description": "Missing the next 2 bytes for mt0 ai 25",
"encoded": h'19',
},
{
"description": "Missing the next 1 byte for mt0 ai 25",
"encoded": h'1900',
},
{
"description": "Missing the next 4 bytes for mt0 ai 26",
"encoded": h'1a',
},
{
"description": "Missing the next 3 bytes for mt0 ai 26",
"encoded": h'1a00',
},
{
"description": "Missing the next 2 bytes for mt0 ai 26",
"encoded": h'1a0000',
},
{
"description": "Missing the next byte for mt0 ai 26",
"encoded": h'1a000000',
},
{
"description": "Missing the next 4 bytes for mt0 ai 27",
"encoded": h'1b000000',
},
{
"description": "Invalid AI: 28",
"encoded": h'1c',
},
{
"description": "Invalid AI: 29",
"encoded": h'1d',
},
{
"description": "Invalid AI: 30",
"encoded": h'1e',
},
{
"description": "Invalid streaming AI: 28",
"encoded": h'fc',
},
{
"description": "Invalid streaming AI: 29",
"encoded": h'fd',
},
{
"description": "Invalid streaming AI: 30",
"encoded": h'fe',
},
{
"description": "bytes: Only 3 bytes, not 4",
"encoded": h'44010203',
},
{
"description": "bytes: Indeterminate bytestring with nothing",
"encoded": h'5f',
},
{
"description": "bytes: Indeterminate bytestring includes a non-bytes chunk",
"encoded": h'5f01ff',
},
{
"description": "utf8: Only 3 bytes, not 4",
"encoded": h'64494554',
},
{
"description": "utf8: Length 20 only has 4 bytes",
"encoded": h'7432303133',
},
{
"description": "utf8: Indeterminate string includes a non-string chunk",
"encoded": h'7f01ff',
},
{
"description": "utf8: no BREAK",
"encoded": h'7f657374726561646d696e',
},
{
"description": "utf8: invalid utf8",
"encoded": h'62c0ae',
},
{
"description": "array: missing item",
"encoded": h'81',
},
{
"description": "array: missing second item",
"encoded": h'8201',
},
{
"description": "array: nested missing item",
"encoded": h'8181818181',
},
{
"description": "array: deeply-nested missing item",
"encoded": h'8181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181',
},
{
"description": "array: invalid item",
"encoded": h'81FE',
},
{
"description": "array: indeterminate without end",
"encoded": h'9f',
},
{
"description": "array: indeterminate with an item, without end",
"encoded": h'9f01',
},
{
"description": "array: streamed containing invalid",
"encoded": h'9fFEff',
},
{
"description": "array: unexpected BREAK",
"encoded": h'91ff',
},
{
"description": "map: expected key",
"encoded": h'a1',
},
{
"description": "map: invalid key",
"encoded": h'a1fe01',
},
{
"description": "map: missing value",
"encoded": h'a16161',
},
{
"description": "map: invalid value",
"encoded": h'a16161fe',
},
{
"description": "map: 1 key expecting 2",
"encoded": h'a20102',
},
{
"description": "map: streaming no BREAK",
"encoded": h'bf',
},
{
"description": "map: streaming, odd number of items",
"encoded": h'bf000103ff',
},
{
"description": "map: streaming missing value",
"encoded": h'bf6161',
},
{
"description": "map: streaming with item, missing BREAK",
"encoded": h'bf616101',
},
{
"description": "map: streaming with invalid key",
"encoded": h'bfFE01',
},
{
"description": "map: streaming with invalid value",
"encoded": h'bf01FE',
},
{
"description": "map: unexpected BREAK in key",
"encoded": h'a1ff',
},
{
"description": "map: unexpected BREAK in value",
"encoded": h'a100ff',
},
{
"description": "unexpected BREAK",
"encoded": h'ff',
},
{
"description": "date: unexpected object instead of offset",
"encoded": h'c1a1616100',
},
{
"description": "date: unexpected object instead of string",
"encoded": h'c0a1616100',
},
],
}
@@ -0,0 +1,526 @@
{
"title": "good",
"description": "Good tests for RFC 8949",
"tests": [
# Integers
{
"description": "u8: non-preferred 0",
"roundtrip": false,
"encoded": h'18 00',
"decoded": 0,
},
{
"description": "u8: max",
"encoded": h'18 ff',
"decoded": 255,
},
{
"description": "s8: -1, not preferred",
"roundtrip": false,
"encoded": h'38 00',
"decoded": -1,
},
{
"description": "s8: min",
"encoded": h'38 ff',
"decoded": -256,
},
{
"description": "u16: max",
"encoded": h'19 ffff',
"decoded": 65535,
},
{
"description": "u16: 0, non-preferred",
"roundtrip": false,
"encoded": h'19 0000',
"decoded": 0,
},
{
"description": "s16: min",
"encoded": h'39 ffff',
"decoded": -65536,
},
{
"description": "s16: -1, non-preferred",
"roundtrip": false,
"encoded": h'39 0000',
"decoded": -1,
},
{
"description": "u32: max",
"encoded": h'1a ffffffff',
"decoded": 0xffffffff,
},
{
"description": "u32: 0, non-preferred",
"roundtrip": false,
"encoded": h'1a 00000000',
"decoded": 0,
},
{
"description": "u32: 65535, non-preferred",
"roundtrip": false,
"encoded": h'1a 0000ffff',
"decoded": 65535,
},
{
"description": "s32: -65537",
"encoded": h'3a 00010000',
"decoded": -65537,
},
{
"description": "s32: min",
"encoded": h'3a ffffffff',
"decoded": -0x100000000,
},
{
"description": "u64: 0, not preferred",
"roundtrip": false,
"encoded": h'1b 0000000000000000',
"decoded": 0,
},
{
"description": "u64: 65535, not preferred",
"roundtrip": false,
"encoded": h'1b 000000000000ffff',
"decoded": 65535,
},
{
"description": "u64: 65536, not preferred",
"roundtrip": false,
"encoded": h'1b 0000000000010000',
"decoded": 65536,
},
{
"description": "u64: MAX_SAFE_INTEGER",
"encoded": h'1b 001fffffffffffff',
"decoded": 0x1fffffffffffff,
},
{
"description": "u64: MIN_SAFE_INTEGER",
"encoded": h'3b 001ffffffffffffe',
"decoded": -0x1fffffffffffff,
},
{
"description": "s64: -1, not preferred",
"roundtrip": false,
"encoded": h'3b 0000000000000000',
"decoded": -1,
},
{
"description": "s64: -65536, not preferred",
"roundtrip": false,
"encoded": h'3b 000000000000ffff',
"decoded": -65536,
},
{
"description": "s64: -65537, not preferred",
"roundtrip": false,
"encoded": h'3b 0000000000010000',
"decoded": -65537,
},
# Floats
## Around 2^53
{
"description": "MAX_SAFE_INTEGER + 1",
"encoded": h'fa 5a000000',
"decoded": 9007199254740992.0,
},
{
"description": "MAX_SAFE_INTEGER + 3",
"encoded": h'fb 4340000000000001',
"decoded": 9007199254740994.0,
},
{
"description": "MIN_SAFE_INTEGER - 1",
"encoded": h'fa da000000',
"decoded": -9007199254740992.0,
},
{
"description": "MIN_SAFE_INTEGER - 3",
"encoded": h'fb c340000000000001',
"decoded": -9007199254740994.0,
},
## f16
{
"description": "f16: Min",
"encoded": h'f9 fbff',
"decoded": -65504.0,
"encodeOptions": {
"avoidInts": true,
},
},
{
"description": "f16: Second-smallest subnormal",
"encoded": h'f9 0002',
"decoded": 1.1920928955078125e-7,
},
{
"description": "f16: Second-smallest subnormal, negative",
"encoded": h'f9 8002',
"decoded": -1.1920928955078125e-7,
},
{
"description": "f16: Largest subnormal",
"roundtrip": false, # precision is lost with subnormal
"encoded": h'f9 03ff',
"decoded": 0.00006097555160522461,
},
{
"description": "f16: Largest subnormal, negative",
"roundtrip": false, # precision is lost with subnormal
"encoded": h'f9 83ff',
"decoded": -0.00006097555160522461,
},
{
"description": "f16: Closest to 1/3",
"encoded": h'f9 3555',
"decoded": 0.333251953125,
},
{
"description": "f16: Closest to -1/3",
"encoded": h'f9 b555',
"decoded": -0.333251953125,
},
{
"description": "f16: Largest number less than one",
"encoded": h'f9 3bff',
"decoded": 0.99951171875,
},
{
"description": "f16: Smallest number greater than -1",
"encoded": h'f9 bbff',
"decoded": -0.99951171875,
},
{
"description": "f16: Smallest number larger than one",
"encoded": h'f9 3c01',
"decoded": 1.0009765625,
},
{
"description": "f16: Largest number less than -1",
"encoded": h'f9 bc01',
"decoded": -1.0009765625,
},
{
"description": "f16: Largest odd number",
"encoded": h'f9 67ff',
"decoded": 2047.0,
"encodeOptions": {
"avoidInts": true,
},
},
{
"description": "f16: Smallest odd number",
"encoded": h'f9 e7ff',
"decoded": -2047.0,
"encodeOptions": {
"avoidInts": true,
},
},
{
"description": "f16: Closest value to pi",
"encoded": h'f9 4248',
"decoded": 3.140625,
},
{
"description": "f16: Closest value to -pi",
"encoded": h'f9 c248',
"decoded": -3.140625,
},
{
"description": "f16: Small positive",
"encoded": h'f9 4100',
"decoded": 2.5,
},
{
"description": "f16: Small negative",
"encoded": h'f9 c100',
"decoded": -2.5,
},
## f32
{
"description": "f32: Min",
"encoded": h'fa ff7fffff',
"decoded": -3.4028234663852886e+38,
},
{
"description": "f32: Smallest subnormal",
"encoded": h'fa 00000001',
"decoded": 1.401298464324817e-45,
},
{
"description": "f32: Smallest subnormal, negative",
"encoded": h'fa 80000001',
"decoded": -1.401298464324817e-45,
},
{
"description": "f32: Largest subnormal",
"encoded": h'fa 007fffff',
"decoded": 1.1754942106924411e-38,
},
{
"description": "f32: Largest subnormal, negative",
"encoded": h'fa 807fffff',
"decoded": -1.1754942106924411e-38,
},
{
"description": "f32: Medium subnormal",
"encoded": h'fa 00001fff',
"decoded": 1.1478035721284577e-41,
},
{
"description": "f32: Medium subnormal, negative",
"encoded": h'fa 80001fff',
"decoded": -1.1478035721284577e-41,
},
{
"description": "f32: smallest positive normal number",
"encoded": h'fa 00800000',
"decoded": 1.1754943508222875e-38,
},
{
"description": "f32: largest negative normal number",
"encoded": h'fa 80800000',
"decoded": -1.1754943508222875e-38,
},
{
"description": "f32: largest number less than one",
"encoded": h'fa 3f7fffff',
"decoded": 0.9999999403953552,
},
{
"description": "f32: smallest number greater than -1",
"encoded": h'fa bf7fffff',
"decoded": -0.9999999403953552,
},
{
"description": "f32: one",
"roundtrip": false, # Not preferred encoding
"encoded": h'fa 3f800000',
"decoded": 1.0,
},
{
"description": "f32: -1",
"roundtrip": false, # Not preferred encoding
"encoded": h'fa bf800000',
"decoded": -1.0,
},
{
"description": "f32: smallest number larger than one",
"encoded": h'fa 3f800001',
"decoded": 1.0000001192092896,
},
{
"description": "f32: 0",
"roundtrip": false, # Not preferred encoding
"encoded": h'fa 00000000',
"decoded": 0.0,
},
{
"description": "f32: -0",
"roundtrip": false, # Not preferred encoding
"encoded": h'fa 80000000',
"decoded": -0.0,
},
{
"description": "f32: 1/3",
"encoded": h'fa 3eaaaaab',
"decoded": 0.3333333432674408,
},
{
"description": "f32: -1/3",
"encoded": h'fa beaaaaab',
"decoded": -0.3333333432674408,
},
{
"description": "f32: pi",
"encoded": h'fa 40490fdb',
"decoded": 3.1415927410125732,
},
{
"description": "f32: -pi",
"encoded": h'fa c0490fdb',
"decoded": -3.1415927410125732,
},
# f64
{
"description": "f64: Max",
"encoded": h'fb 7fefffffffffffff',
"decoded": 1.7976931348623157e+308,
},
{
"description": "f64: Min",
"encoded": h'fb ffefffffffffffff',
"decoded": -1.7976931348623157e+308,
},
{
"description": "f64: Min subnormal",
"encoded": h'fb 0000000000000001',
"decoded": 5e-324,
},
{
"description": "f64: Min subnormal, negative",
"encoded": h'fb 8000000000000001',
"decoded": -5e-324,
},
{
"description": "f64: Largest subnormal",
"encoded": h'fb 000FFFFFFFFFFFFF',
"decoded": 2.225073858507201e-308,
},
{
"description": "f64: Largest subnormal, negative",
"encoded": h'fb 800FFFFFFFFFFFFF',
"decoded": -2.225073858507201e-308,
},
{
"description": "f64: Smallest normal",
"encoded": h'fb 0010000000000000',
"decoded": 2.2250738585072014e-308,
},
{
"description": "f64: Smallest normal, negative",
"encoded": h'fb 8010000000000000',
"decoded": -2.2250738585072014e-308,
},
{
"description": "f64: 1/3",
"encoded": h'fb 3FD5555555555555',
"decoded": 0.3333333333333333,
},
{
"description": "f64: -1/3",
"encoded": h'fb BFD5555555555555',
"decoded": -0.3333333333333333,
},
{
"description": "f64: pi",
"encoded": h'fb 400921FB54442D18',
"decoded": 3.141592653589793,
},
{
"description": "f64: -pi",
"encoded": h'fb C00921FB54442D18',
"decoded": -3.141592653589793,
},
# Bigint
{
"description": "bigint: Positive",
"encoded": h'c2 49 1c0000000000000000',
"decoded": 0x1c0000000000000000,
},
{
"description": "bigint: Negative",
"encoded": h'c3 49 1c0000000000000000',
"decoded": -0x1c0000000000000001,
},
# UTF8 String
{
"description": "string: BOM",
"encoded": h'66 efbbbf424f4d',
"decoded": "\ufeffBOM",
},
{
"description": "string: combining",
"encoded": h'63 75cc88',
"decoded": "ü",
},
{
"description": "string: zalgo, length 1384",
"encoded": h'790568c6b8ccb7cd82cd8bccbfcd8acd98cc9bcc92cc95cc8dcc88ccbdcc8acc88cc8ccc83cda0cc84cd9bccbdcc88cc93cc87cc8ecd9fcd95ccbacc9ccc98cc9dcd9acd88cca9cca7cca4cd95cd99cd89cd96cd9cccadcc9ee1b8b3ccb7cc8ccc89cc80cd80ccbfcc83cc8dcd82cc90cc88cd90cc8ecc85cd98cc94cc86ccb2cd87ccaccca1cd9fcd8eccafccb3cca9cc9cea9cacccb4cda1cc84cd9ecc88cc83cc86cc80cc9acd8acd92cc87cd92cc94cc90cc89cc89cc91ccbecd8bcc84cc92cda0ccabcd89cca8e1b889ccb6ccbecc8ccc83cc94cd9bcd98cc8fcc8dcc8bcd86cda0cca8cca6cd9ccca9cca6ccabcd87ccbcccb0cd9fcca8cd9fcd9cccafccbccca4ccb9cd93cc9ecc97cc9fcc9fcc99cd9accb3ccb3cd9acc9fcca8c8a5ccb6ccbecc8ccc84cd82cd80cc8ccd92ccbecd82cc87cd9ecd90cd92cc88cc94cc8bcc89cc83cd97cd97cd98cc85cc90cc83cd90cc9acd91ccb1ccabcca7cd89ccbccca6cd88cca9ccb1cca8ccbccd8dccaecd8dcd87cd95e1b8a9ccb4cd8ccc8fcc86cc87cd8bcd81cc95cda0cc80cc87cc86cd9ecd98cc87cc89cc81cc84cd83cd9acd94ccbbcca5cd85cd8ecca5cc99cd9ccc9ecca6ccadccbccca8cca2cca1cd87ccb3ccaaccb3cc99ccb2cca2e1ba8accb4cd84cda0cd9dcd9bcc9acd92cc91cc92cc81cd9dcc87cd91cc8dcd9fcd96cca1cca3ccb0cc9eccaacca0cd88cd88ccaacca0ccbacca2cca7ccbbcd95ccb1ccbbccafcca5cd8dea9e8eccb7cc80cc83cc90cc91cd83cc93cd83cd92cc83cd90cda1ccbfcd9ecc95cc82cd98cc94cc88cd82cc8dcd97cca8cc9fcc9fccb2ccb1ccafccbbcd8eccb2cc96cc9dcd9ccd88ccbccd95cca8cca254ccb6cd83cc82cc94cc84cda0cc8bcc9acd97cc8ccd9dcc92cc83cc90cc8acc9bcc83cd86ccbdcc88cc94cca5cc9fccb0cd96cd8ecd8ecd94cd9fcd89cd94cc9ecca7cca7ccaecd89cd89cd94ccbacca9cd87cd88cd87cd8dcd89cd9accabcd93ccbae1b985ccb4cd81cc94cd9dcc92cd82cc95ccbfcc93cc8ccc95cd98cd82ccbdcc95cd83cc86cc9bcc94cc8dcd90cda0cd9ccd95cd87ccbccc9ecc9ccd96ccb3cd85ccb3cd8ecca2ccadccbbcd93ccb1e1bab0ccb8cd97cc95cc9acd8bcc82cc88cc86cd84cc94cd92cc81cc88cc8acc8ecc91cc94cd8accbecd8bcc85ccbecd98cc85cc8acc94cc8bcca5ccb9cca6ccafcd87cd9fcd9acca5cd89cd8dcd9fcd93ccafccbbcd87cca2ccb1ccadccb2cd89ccadcca4cd8dcca1cca3cc9cccb3cab2ccb8cd86cc90cc89cc83cc91ccacccbacc99cca6cc96ccbbcca8ccbbcca2ca89ccb4cd86cc8bcd86cd9ecd90cc81cd81cd8acc9bcda0cc84cc90cd9ecd8accbecca9cd96cd93ccb9ccb3cd8dccbccd89ccafcd8ecca5cd94cd85cc9dcca6cd8ecd93cca2c6bdccb7cd84cd86cc83cc95cd90cc92cc98cd93cc9dcca8cca4ccbcccafcd85ccafcd8dcd94cc9dcd87ccaccd9accaecca5ccb1ccbcccb1cca0ccadcc98cca3cc9dccbbcd9fccbccc97e1bbbcccb6cc94cd80cd92cd80cda0cc8fcc81cd9ecc92cd83cd92cd84cc9bcd92cd8ccd86cc8dcc94cc84cda1cd91cd83cd84cc8dcd97cda0cd80cca2cd88cd93cd8dcd93cca1cca4ccafcca1cca4ccaccca0ccb9cd9acc9ecc99ccbaccb1cca2cc9ccd95cca4cc99cca9ccb0cd8eccaee1b9bdccb7cc95cd92cc9acc81cd91cc90cda0cc97cca5cca9cd94cd87cca3cd99ccb1cc9ecca7cca6ccb2ccbbcc96cc9fccb3cc99ccaecca4cca2cca5cca3e1b8b8ccb6cc8ecd81cda1cc83cd91cd97cd8dcc9fcd88cc98ccb1ccb1cd8dcc9fcca9ccb3cd8eccbcccabcca0cd85cd8dcc96cd9acca0cd93cd9fcca4cca9cd8dccb9cc9fcca5ccbbcd9fc7b0ccb5cc83cd8cccbecc8acd8bcd89cd85ccaacd89ccb1cca0cd88cca2ccaccd9ccd8dccadccaaccb9cca6ccb1cca0cca1cd88',
"decoded": "Ƹ̷̧̛͕̺̜̘̝͚͈̩̤͕͙͉͖̭̞͂͋̿͊̒̍̈̽̊̈̌̃̄͛̽̈̓̇̎͘̕͟͜͠ḳ̷̡̲͇̬͎̯̳̩̜̌̉̀̀̿̃̍͂̐̈͐̎̅̔̆͘͟Ꜭ̴̨̫͉̄̈̃̆̀͊͒̇͒̔̐̉̉̑̾͋̄̒̚͡͞͠ḉ̶̨̨̨̦̩̦̫͇̼̰̯̼̤̹͓̞̗̟̟̙͚̳̳͚̟̾̌̃̔͛̏̍̋͆͘͜͟͟͜͠ȥ̶̧̨̱̫͉̼̦͈̩̱̼͍̮͍͇͕̾̌̄͂̀̌͒̾͂̇͐͒̈̔̋̉̃͗͗̅̐̃͐͑͘̚͞ḩ̴̨̢̡̢͚͔̻̥͎̥̙̞̦̭̼͇̳̪̳̙̲͌̏̆̇͋́̀̇̆̇̉́̄̓̕͘͜͠͞ͅẊ̴̡̢̧͖̣̰̞̪̠͈͈̪̠̺̻͕̱̻̯̥͍̈́͛͒̑̒́̇͑̍̚͟͠͝͝ꞎ̷̨̨̢̟̟̲̱̯̻͎̲̖̝͈̼͕̀̃̐̑̓̓̓͒̃͐̿̂̔̈͂̍͗̕͘͜͡͞Ţ̶̧̛̥̟̰͖͎͎͔͉͔̞̮͉͉͔̺̩͇͈͇͍͉͚̫͓̺̓̂̔̄̋͗̌̒̃̐̊̃͆̽̈̔̚͟͠͝ṅ̴̢̛͕͇̼̞̜͖̳̳͎̭̻͓̱́̔̒͂̿̓̌͂̽̓̆̔̍͐̕̕͘̕͜͝͠ͅḀ̸̢̡̹̦̯͇͚̥͉͍͓̯̻͇̱̭̲͉̭̤͍̣̜̳̆̀͗͋̂̈̆̈́̔͒́̈̊̎̑̔͊̾͋̅̾̅̊̔̋̕̚͘͟͟ʲ̸̨̢̬̺̙̦̖̻̻͆̐̉̃̑ʉ̴̢̛̩͖͓̹̳͍̼͉̯͎̥͔̝̦͎͓͆̋͆͐́́͊̄̐͊̾͞͠͞ͅƽ̷̨̘͓̝̤̼̯̯͍͔̝͇̬͚̮̥̱̼̱̠̭̘̣̝̻̼̗̈́͆̃͐̒̕͟ͅỼ̶̢̡̡̢̛͈͓͍͓̤̯̤̬̠̹͚̞̙̺̱̜͕̤̙̩̰͎̮̔̀͒̀̏́̒̓͒̈́͒͌͆̍̔̄͑̓̈́̍͗̀͠͞͡͠ṽ̷̧̢̗̥̩͔͇̣͙̱̞̦̲̻̖̟̳̙̮̤̥̣͒́͑̐̕̚͠Ḹ̶͍̟͈̘̱̱͍̟̩̳͎̼̫̠͍̖͚̠͓̤̩͍̹̟̥̻̎́̃͑͗͟͟͡ͅǰ̵̢̡͉̪͉̱̠͈̬͍̭̪̹̦̱̠͈̃͌̾̊͋͜ͅ",
},
# Date
{
"description": "Date: 0 epoch",
"encoded": h'c100',
"decoded": 1(0),
},
{
"description": "Date: 1 epoch",
"encoded": h'c101',
"decoded": 1(1),
},
{
"description": "Date: -1 epoch",
"encoded": h'c120',
"decoded": 1(-1),
},
# TODO: Large tag numbers, which have been reserved by IANA
# Map
{
"description": "Map: -0 key",
"roundtrip": false, # JS Map converts -0 to 0 in map key
"encoded": h'a1f9800080',
"decoded": {
-0.0: [],
},
},
{
"description": "Map: interesting keys",
"roundtrip": false,
"encoded": h'b81a808081008081808081810080f580f480f680f7800080613080fb3fb999999999999a8001802080f97c0080f9fc0080f97e0080c2491c000000000000000080a080a1808080a1a08080a1a18080808040804100806080616180c10080',
"decoded": {
[]: [],
[0]: [],
[[]]: [],
[[0]]: [],
true: [],
false: [],
null: [],
undefined: [],
0: [],
"0": [],
0.1: [],
1: [],
-1: [],
Infinity: [],
-Infinity: [],
NaN: [],
0x1c0000000000000000: [],
{}: [],
{[]: []}: [],
{{}: []}: [],
{{[]: []}: []}: [],
h'': [],
h'00': [],
"": [],
"a": [],
1(0): []
},
},
{
"description": "array: deeply-nested",
"encoded": h'8181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818181818100',
"decoded": [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[0]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],
},
{
"description": "map: deeply-nested key",
"encoded": h'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
"decoded": {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{0: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0}: 0},
},
{
"description": "map: deeply-nested value",
"encoded": h'a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a100a10000',
"decoded": {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: {0: 0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
},
# JS edge cases
{
"description": "JS: __proto__ should be escaped (security)",
"encoded": h'a1695f5f70726f746f5f5f00',
"decoded": {"__proto__": 0},
}
],
}
@@ -0,0 +1,152 @@
// CBOR Working Group conformance corpus
// (https://github.com/cbor-wg/cbor-test-vectors).
//
// Two fixture files live next to this test:
//
// * `cbor_wg/rfc8949_good.edn` — 88 well-formed payloads. Each must
// decode without error.
// * `cbor_wg/rfc8949_bad.edn` — 47 malformed payloads. Each MUST be
// rejected by the decoder per RFC 8949 §3.
//
// The fixtures are EDN (CBOR Diagnostic Notation, RFC 8610) — we only
// pull the `"encoded": h'…'` hex literals because that's what we need
// to drive the decoder. The expected `decoded` value is left to the
// other test files (rfc8949_appendix_a, upstream_appendix_a) which use
// the JSON-encoded corpus.
module main
import encoding.cbor
import encoding.hex
import os
const wg_dir = os.join_path(os.dir(@FILE), 'cbor_wg')
// extract_hex_literals pulls every `"encoded": h'…'` value out of an EDN
// file. The hex string can contain whitespace (visual grouping per
// RFC 8610) — we strip it before decoding.
fn extract_hex_literals(text string) []string {
mut out := []string{}
mut i := 0
needle := '"encoded": h\''
for {
idx := text.index_after(needle, i) or { break }
start := idx + needle.len
end := text.index_after("'", start) or { break }
raw := text[start..end]
mut clean := []u8{cap: raw.len}
for c in raw {
if c == ` ` || c == `\t` || c == `\n` || c == `\r` {
continue
}
clean << c
}
out << clean.bytestr()
i = end + 1
}
return out
}
fn test_extractor_sanity() {
good := os.read_file(os.join_path(wg_dir, 'rfc8949_good.edn')) or {
panic('cannot read good.edn: ${err}')
}
bad := os.read_file(os.join_path(wg_dir, 'rfc8949_bad.edn')) or {
panic('cannot read bad.edn: ${err}')
}
good_hexes := extract_hex_literals(good)
bad_hexes := extract_hex_literals(bad)
assert good_hexes.len == 88, 'good corpus drift: ${good_hexes.len} (expected 88)'
assert bad_hexes.len == 47, 'bad corpus drift: ${bad_hexes.len} (expected 47)'
}
fn test_cbor_wg_good_corpus() {
text := os.read_file(os.join_path(wg_dir, 'rfc8949_good.edn'))!
hexes := extract_hex_literals(text)
// The corpus deliberately stresses 256+ deep nesting; raise the cap.
opts := cbor.DecodeOpts{
max_depth: 4096
}
mut failures := []string{}
for hex_str in hexes {
bytes := hex.decode(hex_str) or {
failures << '${hex_str}: hex decode: ${err}'
continue
}
cbor.decode[cbor.Value](bytes, opts) or {
failures << '${hex_str}: ${err}'
continue
}
}
if failures.len > 0 {
for f in failures {
eprintln('GOOD-FAIL: ${f}')
}
assert false, '${failures.len}/${hexes.len} good vectors rejected'
}
}
// Per-major-type files (mt0..mt7) and the streaming/indefinite suite all
// hold well-formed entries. Each must decode without error.
const mt_files = [
'appA_mt0.edn',
'appA_mt1.edn',
'appA_mt2.edn',
'appA_mt3.edn',
'appA_mt4.edn',
'appA_mt5.edn',
'appA_mt6.edn',
'appA_mt7-float.edn',
'appA_mt7-simple.edn',
'appA_streaming.edn',
]
fn test_cbor_wg_per_major_type_corpus() {
mut total := 0
mut failures := []string{}
for fname in mt_files {
text := os.read_file(os.join_path(wg_dir, fname)) or {
panic('cannot read ${fname}: ${err}')
}
hexes := extract_hex_literals(text)
assert hexes.len > 0, '${fname} has no entries'
for hex_str in hexes {
total++
bytes := hex.decode(hex_str) or {
failures << '${fname} ${hex_str}: hex: ${err}'
continue
}
cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{}) or {
failures << '${fname} ${hex_str}: ${err}'
continue
}
}
}
if failures.len > 0 {
for f in failures {
eprintln('MT-FAIL: ${f}')
}
assert false, '${failures.len}/${total} per-major-type vectors rejected'
}
assert total >= 80, 'corpus too small: ${total}'
}
fn test_cbor_wg_bad_corpus() {
text := os.read_file(os.join_path(wg_dir, 'rfc8949_bad.edn'))!
hexes := extract_hex_literals(text)
mut accepted_anyway := []string{}
for hex_str in hexes {
bytes := hex.decode(hex_str) or {
// Malformed at the hex layer is still a rejection; skip.
continue
}
if v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{}) {
accepted_anyway << '${hex_str} → ${v.type_name()}'
}
}
if accepted_anyway.len > 0 {
for a in accepted_anyway {
eprintln('BAD-ACCEPTED: ${a}')
}
assert false, '${accepted_anyway.len}/${hexes.len} malformed vectors were not rejected'
}
}
@@ -0,0 +1,258 @@
// Real-world conformance: drives CBOR-Web-Token (RFC 8392) and COSE
// (RFC 8152) sample structures. These are the canonical CBOR payloads
// used by IoT auth, OAuth 2.0 PoP, and EAT (RFC 9711). They exercise
// canonical encoding, signed/MAC/COSE_Sign1 structures, and standard
// claim-set integer keys.
module main
import encoding.cbor
import encoding.hex
fn h_(s string) []u8 {
return hex.decode(s) or { panic('bad hex ${s}') }
}
fn b_eq(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}
// --------------------------------------------------------------------
// RFC 8392 §A.1 — Example CWT Claims Set
//
// {
// 1: "coap://as.example.com", // iss
// 2: "erikw", // sub
// 3: "coap://light.example.com",// aud
// 4: 1444064944, // exp
// 5: 1443944944, // nbf
// 6: 1443944944, // iat
// 7: h'0b71' // cti
// }
const a1_claims_hex = 'a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77' +
'037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612ae' +
'b0051a5610d9f0061a5610d9f007420b71'
fn test_rfc8392_a1_cwt_claims_set() {
v := cbor.decode[cbor.Value](h_(a1_claims_hex), cbor.DecodeOpts{}) or {
panic('decode CWT claims: ${err}')
}
if v !is cbor.Map {
assert false, 'CWT claim set must decode to a Map'
return
}
m := v as cbor.Map
assert m.pairs.len == 7
// Helper: find pair by integer key.
mut by_key := map[u64]cbor.Value{}
for pair in m.pairs {
key := pair.key
if key !is cbor.IntNum {
assert false, 'CWT claim key must be an integer (got ${key.type_name()})'
return
}
k := key as cbor.IntNum
assert !k.negative
by_key[k.magnitude] = pair.value
}
// iss / sub / aud / exp / nbf / iat / cti
iss := by_key[1] or {
assert false, 'missing iss'
return
}
assert (iss as cbor.Text).value == 'coap://as.example.com'
sub := by_key[2] or {
assert false, 'missing sub'
return
}
assert (sub as cbor.Text).value == 'erikw'
aud := by_key[3] or {
assert false, 'missing aud'
return
}
assert (aud as cbor.Text).value == 'coap://light.example.com'
exp := by_key[4] or {
assert false, 'missing exp'
return
}
exp_int := exp as cbor.IntNum
assert !exp_int.negative && exp_int.magnitude == 1444064944
nbf := by_key[5] or {
assert false, 'missing nbf'
return
}
nbf_int := nbf as cbor.IntNum
assert !nbf_int.negative && nbf_int.magnitude == 1443944944
iat := by_key[6] or {
assert false, 'missing iat'
return
}
iat_int := iat as cbor.IntNum
assert !iat_int.negative && iat_int.magnitude == 1443944944
cti := by_key[7] or {
assert false, 'missing cti'
return
}
cti_bs := cti as cbor.Bytes
assert b_eq(cti_bs.data, [u8(0x0b), 0x71])
// Re-encode through the Value tree: must be byte-identical (RFC 8392
// claim sets are already in canonical form per §7).
out := cbor.encode_value(v, cbor.EncodeOpts{})!
assert b_eq(out, h_(a1_claims_hex)), 'CWT round-trip mismatch'
}
// --------------------------------------------------------------------
// RFC 8392 §A.3 — COSE_Mac0-tagged CWT (tag 17 = CBOR_Tag_COSE_Mac0)
// The outer is `61(...)` = tag 61 (CWT) wrapping a COSE_Mac0 (tag 17).
// We don't validate the MAC — only the CBOR structure parses cleanly,
// the protected header is a bstr, the claim payload is a bstr containing
// the §A.1 claims, and the tag is correctly identified.
// Tag 61 (CWT) → tag 17 (COSE_Mac0) → [protected={1:5}, {}, payload, mac_tag]
// Constructed from §A.1 claims + §A.3 example MAC.
const a3_mac_hex = 'd83d' + // CWT tag
'd1' + // COSE_Mac0 tag
'84' + // array(4)
'43a10105' + // bstr(3): {1:5} (HMAC 256/64)
'a0' + // {}
'5850' + a1_claims_hex + // bstr(80): claims
'48093101ef6d789200' // bstr(8): MAC
fn test_rfc8392_a3_cose_mac0_cwt() {
v := cbor.decode[cbor.Value](h_(a3_mac_hex), cbor.DecodeOpts{}) or {
panic('decode CWT-Mac0: ${err}')
}
// Outer is tag 61 (CWT).
if v !is cbor.Tag {
assert false, 'expected tag 61'
return
}
cwt_tag := v as cbor.Tag
assert cwt_tag.number == 61, 'outer tag = ${cwt_tag.number}, want 61'
// Inner is tag 17 (COSE_Mac0).
inner := cwt_tag.content()
if inner !is cbor.Tag {
assert false, 'expected tag 17 inside CWT'
return
}
mac0 := inner as cbor.Tag
assert mac0.number == 17, 'inner tag = ${mac0.number}, want 17'
// COSE_Mac0 = [protected, unprotected, payload, tag]
body := mac0.content()
if body !is cbor.Array {
assert false, 'COSE_Mac0 must be array'
return
}
arr := body as cbor.Array
assert arr.elements.len == 4, 'COSE_Mac0 must have 4 elements, got ${arr.elements.len}'
// protected is a bstr wrapping a CBOR-encoded map.
protected_bs := arr.elements[0] as cbor.Bytes
protected_map := cbor.decode[cbor.Value](protected_bs.data, cbor.DecodeOpts{}) or {
panic('decode protected header: ${err}')
}
assert protected_map is cbor.Map
// unprotected is an empty map.
assert arr.elements[1] is cbor.Map
assert (arr.elements[1] as cbor.Map).pairs.len == 0
// payload is a bstr that decodes to the §A.1 claims set.
payload_bs := arr.elements[2] as cbor.Bytes
claims := cbor.decode[cbor.Value](payload_bs.data, cbor.DecodeOpts{}) or {
panic('decode payload: ${err}')
}
assert claims is cbor.Map
claims_map := claims as cbor.Map
assert claims_map.pairs.len == 7, 'expected 7 claims, got ${claims_map.pairs.len}'
// Round-trip: re-encode the entire structure and compare bytes.
out := cbor.encode_value(v, cbor.EncodeOpts{})!
assert b_eq(out, h_(a3_mac_hex)), 'CWT-Mac0 round-trip mismatch'
}
// --------------------------------------------------------------------
// RFC 8152 §C.2.1 — COSE_Sign1 single-signer ECDSA example
// 18([h'a201260300', {}, h'546869732069732074686520636f6e74656e742e',
// h'6520bbaf2081d7e0ed0f95f76eb0733d667005f7467cec4b87b9381a6ba1ed' +
// 'e8e00df29f32a37230f39a842a54821fdd223092819d7728efb9d3a0080b75'])
//
// The fully-encoded form is published in the working group test
// vectors. We encode it from its components to validate that the
// pieces round-trip — that's the meaningful interop check (the actual
// signature isn't verified).
fn test_cose_sign1_structure() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(18) // COSE_Sign1
p.pack_array_header(4)
// protected (bstr containing {1: -7}) −7 = ECDSA w/ SHA-256
p.pack_bytes(h_('a10126'))
// unprotected (empty map)
p.pack_map_header(0)
// payload
p.pack_bytes('This is the content.'.bytes())
// signature (truncated example)
p.pack_bytes(h_('8eb33e4ca31d1c465ab05aac34cc6b23d58fef5c083106c4d25a91aef0b0117e2af9a291aa32e14ab834dc56ed2a223444547e01f11d3b0916e5a4c345cacb36'))
out := p.bytes()
// Decode back and validate the COSE_Sign1 shape.
v := cbor.decode[cbor.Value](out, cbor.DecodeOpts{}) or { panic('decode Sign1: ${err}') }
tag := v as cbor.Tag
assert tag.number == 18, 'tag = ${tag.number}, want 18 (COSE_Sign1)'
body := tag.content() as cbor.Array
assert body.elements.len == 4
protected := body.elements[0] as cbor.Bytes
hdr := cbor.decode[cbor.Value](protected.data, cbor.DecodeOpts{}) or {
panic('decode protected: ${err}')
}
hdr_map := hdr as cbor.Map
assert hdr_map.pairs.len == 1
// alg label = 1, value = -7
alg_key := hdr_map.pairs[0].key as cbor.IntNum
assert !alg_key.negative && alg_key.magnitude == 1
alg_val := hdr_map.pairs[0].value as cbor.IntNum
assert alg_val.negative && alg_val.magnitude == 6 // -7 = -1 - 6
payload := body.elements[2] as cbor.Bytes
assert payload.data.bytestr() == 'This is the content.'
// Roundtrip the whole thing through Value tree.
rt := cbor.encode_value(v, cbor.EncodeOpts{})!
assert b_eq(rt, out), 'COSE_Sign1 round-trip mismatch'
}
// --------------------------------------------------------------------
// RFC 8152 §3 — Sig_structure used as Signature Input
// Sig_structure = [context, body_protected, external_aad, payload]
// This covers canonical encoding requirements (§4.4): when computing
// the to-be-signed bytes, the structure MUST be deterministically encoded.
fn test_sig_structure_canonical() {
mut p := cbor.new_packer(cbor.EncodeOpts{ canonical: true })
p.pack_array_header(4)
p.pack_text('Signature1')
p.pack_bytes(h_('a10126'))
p.pack_bytes([]u8{}) // external_aad
p.pack_bytes('payload bytes'.bytes())
encoded := p.bytes()
// Re-encode in canonical mode through the Value tree must be identical.
v := cbor.decode[cbor.Value](encoded, cbor.DecodeOpts{}) or { panic(err) }
mut p2 := cbor.new_packer(cbor.EncodeOpts{ canonical: true })
p2.pack_value(v)!
rt := p2.bytes()
assert b_eq(rt, encoded), 'Sig_structure canonical round-trip differs'
}
@@ -0,0 +1,284 @@
// Generic encode[T]/decode[T] coverage. Exercises every supported V
// type family — primitives, arrays, maps, structs (with attributes),
// optional fields, enums, RawMessage, Marshaler/Unmarshaler — and
// asserts byte-exact output for at least one case per family so we
// catch silent encoding drift.
module main
import encoding.cbor
import encoding.hex
fn h(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
fn beq(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}
// ---------------------------------------------------------------------
// Primitive round-trips
// ---------------------------------------------------------------------
fn test_round_trip_primitives() {
bytes_bool := cbor.encode[bool](true, cbor.EncodeOpts{})!
assert cbor.decode[bool](bytes_bool, cbor.DecodeOpts{})! == true
bytes_i32 := cbor.encode[i32](-42, cbor.EncodeOpts{})!
assert cbor.decode[i32](bytes_i32, cbor.DecodeOpts{})! == -42
bytes_u64 := cbor.encode[u64](u64(0x1234_5678_9abc_def0), cbor.EncodeOpts{})!
assert cbor.decode[u64](bytes_u64, cbor.DecodeOpts{})! == 0x1234_5678_9abc_def0
bytes_f64 := cbor.encode[f64](3.141592653589793, cbor.EncodeOpts{})!
assert cbor.decode[f64](bytes_f64, cbor.DecodeOpts{})! == 3.141592653589793
bytes_str := cbor.encode[string]('hello, 世界', cbor.EncodeOpts{})!
assert cbor.decode[string](bytes_str, cbor.DecodeOpts{})! == 'hello, 世界'
}
// ---------------------------------------------------------------------
// Arrays and maps
// ---------------------------------------------------------------------
fn test_array_round_trip() {
src := [1, 2, 3, 4, 5]
bytes := cbor.encode[[]int](src, cbor.EncodeOpts{})!
got := cbor.decode[[]int](bytes, cbor.DecodeOpts{})!
assert got == src
}
fn test_map_round_trip() {
src := {
'a': 1
'b': 2
'c': 3
}
bytes := cbor.encode[map[string]int](src, cbor.EncodeOpts{})!
got := cbor.decode[map[string]int](bytes, cbor.DecodeOpts{})!
for k, v in src {
assert got[k] == v
}
assert got.len == src.len
}
fn test_nested_array_map() {
src := [
{
'k': 'v1'
},
{
'k': 'v2'
},
]
bytes := cbor.encode[[]map[string]string](src, cbor.EncodeOpts{})!
got := cbor.decode[[]map[string]string](bytes, cbor.DecodeOpts{})!
assert got.len == 2
assert got[0]['k'] == 'v1'
assert got[1]['k'] == 'v2'
}
// ---------------------------------------------------------------------
// Structs — attributes, optional, rename strategies
// ---------------------------------------------------------------------
struct Person {
name string
age int
}
fn test_struct_basic() {
p := Person{
name: 'Alice'
age: 42
}
bytes := cbor.encode[Person](p, cbor.EncodeOpts{})!
got := cbor.decode[Person](bytes, cbor.DecodeOpts{})!
assert got.name == 'Alice'
assert got.age == 42
}
struct WithAttrs {
user_id string @[cbor: 'uid']
password string @[skip]
internal string @[cbor: '-']
keep string
}
fn test_struct_attributes() {
p := WithAttrs{
user_id: 'u-1'
password: 'secret'
internal: 'hidden'
keep: 'visible'
}
bytes := cbor.encode[WithAttrs](p, cbor.EncodeOpts{})!
// Decode generically to inspect structure.
v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})!
assert v is cbor.Map
if v is cbor.Map {
mut keys := []string{}
for pair in v.pairs {
if pair.key is cbor.Text {
keys << pair.key.value
}
}
assert 'uid' in keys
assert 'keep' in keys
assert 'password' !in keys
assert 'internal' !in keys
}
got := cbor.decode[WithAttrs](bytes, cbor.DecodeOpts{})!
assert got.user_id == 'u-1'
assert got.keep == 'visible'
}
struct WithOption {
name string
tag ?string
}
fn test_struct_option_field() {
none_p := WithOption{
name: 'a'
tag: none
}
bytes := cbor.encode[WithOption](none_p, cbor.EncodeOpts{})!
got := cbor.decode[WithOption](bytes, cbor.DecodeOpts{})!
assert got.name == 'a'
assert got.tag == none
some_p := WithOption{
name: 'b'
tag: ?string('hot')
}
bytes2 := cbor.encode[WithOption](some_p, cbor.EncodeOpts{})!
got2 := cbor.decode[WithOption](bytes2, cbor.DecodeOpts{})!
assert got2.name == 'b'
assert got2.tag != none
assert got2.tag or { '' } == 'hot'
}
@[cbor_rename_all: 'kebab-case']
struct WithRename {
user_name string
user_age int
}
fn test_struct_rename_all() {
p := WithRename{
user_name: 'Bob'
user_age: 30
}
bytes := cbor.encode[WithRename](p, cbor.EncodeOpts{})!
v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{})!
if v is cbor.Map {
mut keys := []string{}
for pair in v.pairs {
if pair.key is cbor.Text {
keys << pair.key.value
}
}
assert 'user-name' in keys
assert 'user-age' in keys
}
// And the rename round-trips.
got := cbor.decode[WithRename](bytes, cbor.DecodeOpts{})!
assert got.user_name == 'Bob'
assert got.user_age == 30
}
// ---------------------------------------------------------------------
// Enum
// ---------------------------------------------------------------------
enum Color {
red
green
blue
}
fn test_enum_round_trip() {
bytes := cbor.encode[Color](Color.green, cbor.EncodeOpts{})!
got := cbor.decode[Color](bytes, cbor.DecodeOpts{})!
assert got == Color.green
}
// ---------------------------------------------------------------------
// RawMessage — preserves bytes byte-for-byte
// ---------------------------------------------------------------------
fn test_raw_message_round_trip() {
original := h('a26161016162820203')
raw := cbor.decode[cbor.RawMessage](original, cbor.DecodeOpts{})!
again := cbor.encode[cbor.RawMessage](raw, cbor.EncodeOpts{})!
assert beq(again, original)
}
// ---------------------------------------------------------------------
// Marshaler / Unmarshaler — user-controlled wire format
// ---------------------------------------------------------------------
struct Ipv4 {
mut:
octets [4]u8
}
pub fn (ip Ipv4) to_cbor() []u8 {
mut p := cbor.new_packer(cbor.EncodeOpts{ initial_cap: 8 })
p.pack_bytes([ip.octets[0], ip.octets[1], ip.octets[2], ip.octets[3]])
return p.bytes().clone()
}
pub fn (mut ip Ipv4) from_cbor(data []u8) ! {
mut u := cbor.new_unpacker(data, cbor.DecodeOpts{})
bytes := u.unpack_bytes()!
if bytes.len != 4 {
return error('Ipv4: expected 4 bytes, got ${bytes.len}')
}
ip.octets[0] = bytes[0]
ip.octets[1] = bytes[1]
ip.octets[2] = bytes[2]
ip.octets[3] = bytes[3]
}
fn test_marshaler_round_trip() {
ip := Ipv4{
octets: [u8(192), 168, 1, 1]!
}
bytes := cbor.encode[Ipv4](ip, cbor.EncodeOpts{})!
// Wire bytes: 0x44 (bytes len 4) followed by 4 octets.
assert beq(bytes, h('44c0a80101'))
got := cbor.decode[Ipv4](bytes, cbor.DecodeOpts{})!
assert got.octets[0] == 192
assert got.octets[1] == 168
assert got.octets[2] == 1
assert got.octets[3] == 1
}
// ---------------------------------------------------------------------
// Integer-range checks on decode
// ---------------------------------------------------------------------
fn test_int_range_overflow_rejected() {
// 256 doesn't fit u8.
bytes := cbor.encode[u16](u16(256), cbor.EncodeOpts{})!
if _ := cbor.decode[u8](bytes, cbor.DecodeOpts{}) {
assert false, 'expected u8 range error'
}
}
fn test_negative_to_unsigned_rejected() {
bytes := cbor.encode[i64](-1, cbor.EncodeOpts{})!
if _ := cbor.decode[u64](bytes, cbor.DecodeOpts{}) {
assert false, 'expected u64 range error'
}
}
@@ -0,0 +1,831 @@
// Hardening tests: adversarial payloads, RFC 8949 strictness boundaries
// on both encode and decode, and round-trip invariants for the parts
// the rest of the suite doesn't cover (sub-second time, NaN dedup,
// canonical float, indef-string composition rules).
module main
import encoding.cbor
import encoding.hex
import io
import time
fn b(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
// ---------------------------------------------------------------------
// Decode bounds: a length argument can be up to u64::max on the wire,
// but the host's int is i32 on 32-bit targets. Naively casting the
// argument before bounds-checking aborts with `array.slice: invalid
// slice index (start>end)` or `negative .cap`. Every length read path
// must clamp before any int(...) cast.
// ---------------------------------------------------------------------
fn test_unpack_text_rejects_u64_max_length() {
mut u := cbor.new_unpacker(b('7bffffffffffffffff'), cbor.DecodeOpts{})
if _ := u.unpack_text() {
assert false, 'u64::max text length must not panic the decoder'
}
}
fn test_unpack_bytes_rejects_u64_max_length() {
mut u := cbor.new_unpacker(b('5bffffffffffffffff'), cbor.DecodeOpts{})
if _ := u.unpack_bytes() {
assert false, 'u64::max bytes length must not panic the decoder'
}
}
fn test_unpack_text_rejects_i32_overflow_length() {
// 7a 80000000 = text(2^31) — exactly the i32 wrap point.
mut u := cbor.new_unpacker(b('7a80000000'), cbor.DecodeOpts{})
if _ := u.unpack_text() {
assert false, 'expected EOF rejection at 2^31 boundary'
}
}
fn test_skip_value_rejects_oversized_text_length() {
mut u := cbor.new_unpacker(b('7bffffffffffffffff'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject u64::max text length'
}
}
fn test_skip_value_rejects_oversized_bytes_length() {
mut u := cbor.new_unpacker(b('5bffffffffffffffff'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject u64::max bytes length'
}
}
fn test_skip_value_rejects_oversized_indef_chunk() {
// 7f 7b ff..ff: indef text whose first chunk claims u64::max bytes.
mut u := cbor.new_unpacker(b('7f7bffffffffffffffffff'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject u64::max chunk length'
}
}
// ---------------------------------------------------------------------
// Pre-allocation safety: the array/map header argument is a u64. The
// decoder must refuse any value it can't realistically allocate (cast
// overflow at i64::max, or item count > remaining bytes), instead of
// triggering a `negative .cap` panic or attempting a multi-GB malloc.
// ---------------------------------------------------------------------
fn test_value_decode_rejects_array_header_at_i64_max() {
mut u := cbor.new_unpacker(b('9b7fffffffffffffff'), cbor.DecodeOpts{})
if _ := u.unpack_value() {
assert false, 'unpack_value must reject implausible array length'
}
}
fn test_value_decode_rejects_map_header_at_i64_max() {
mut u := cbor.new_unpacker(b('bb7fffffffffffffff'), cbor.DecodeOpts{})
if _ := u.unpack_value() {
assert false, 'unpack_value must reject implausible map length'
}
}
fn test_value_decode_rejects_array_longer_than_buffer() {
// 9a 00ffffff = array of ~16M items in a 5-byte payload.
mut u := cbor.new_unpacker(b('9a00ffffff'), cbor.DecodeOpts{})
if _ := u.unpack_value() {
assert false, 'unpack_value must reject array longer than buffer'
}
}
fn test_value_decode_rejects_map_longer_than_buffer() {
mut u := cbor.new_unpacker(b('ba00ffffff'), cbor.DecodeOpts{})
if _ := u.unpack_value() {
assert false, 'unpack_value must reject map longer than buffer'
}
}
// ---------------------------------------------------------------------
// skip_value strictness: must reject every malformed shape that the
// typed unpack_* paths reject, otherwise malformed CBOR slips through
// RawMessage, Unmarshaler, and unknown-field skipping into downstream
// consumers.
// ---------------------------------------------------------------------
fn test_skip_value_rejects_simple_below_32() {
// f8 1f = simple(1-byte form, value 31). RFC 8949 §3.3: values < 32
// must use the inline form (info 0..23).
mut u := cbor.new_unpacker(b('f81f'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject simple < 32 in 1-byte form'
}
}
fn test_unpack_raw_rejects_simple_below_32() {
mut u := cbor.new_unpacker(b('f81f'), cbor.DecodeOpts{})
if _ := u.unpack_raw() {
assert false, 'unpack_raw must reject simple < 32 in 1-byte form'
}
}
// ---------------------------------------------------------------------
// deny_duplicate_keys covers every map decode path (typed map, struct,
// Value tree definite, Value tree indefinite). RFC 8949 §5.6: map keys
// "should be unique"; deny_duplicate_keys turns that into a hard error.
// ---------------------------------------------------------------------
fn test_value_decode_rejects_duplicate_keys_when_opted_in() {
// a2 01 01 01 02 = {1: 1, 1: 2} — duplicate uint key 1.
bytes := b('a201010102')
if _ := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{ deny_duplicate_keys: true }) {
assert false, 'expected duplicate-key rejection on Value path'
}
}
fn test_value_decode_rejects_indef_map_duplicate_keys_when_opted_in() {
// bf 01 01 01 02 ff = indef map with key 1 twice.
bytes := b('bf01010102ff')
if _ := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{ deny_duplicate_keys: true }) {
assert false, 'expected duplicate-key rejection on indef Value map'
}
}
fn test_value_decode_tolerates_duplicates_by_default() {
// Default mode is permissive (matches Go encoding/cbor and QCBOR);
// the option is opt-in.
bytes := b('a201010102')
v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{}) or {
assert false, 'default mode must accept duplicates: ${err}'
return
}
assert v is cbor.Map
}
fn test_value_decode_detects_nan_keys_as_duplicate() {
// bf f9 7e00 01 f9 7e00 02 ff = {NaN: 1, NaN: 2}, both half qNaN.
// IEEE 754 says NaN != NaN, but RFC §5.6 dedup is over the encoded
// bytes — two identical NaN encodings ARE equal.
bytes := b('bff97e0001f97e0002ff')
if _ := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{ deny_duplicate_keys: true }) {
assert false, 'two NaN keys must be detected as duplicates'
}
}
fn test_value_decode_keeps_distinct_float_widths_separate() {
// Same numeric value, different wire widths → distinct data items
// per RFC §5.6 (encoded form match).
// bf f9 3c00 01 fa 3f800000 02 ff = {f16(1.0): 1, f32(1.0): 2}
bytes := b('bff93c0001fa3f80000002ff')
v := cbor.decode[cbor.Value](bytes, cbor.DecodeOpts{ deny_duplicate_keys: true })!
assert v is cbor.Map
if v is cbor.Map {
assert v.pairs.len == 2
}
}
// ---------------------------------------------------------------------
// Encoder indef state machine. RFC 8949 §3.2.3: an indef text/bytes
// string may only contain definite-length strings of the same major
// type. Opening any other indef container inside it is malformed.
// ---------------------------------------------------------------------
fn test_pack_text_indef_rejects_self_nesting() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_text_indef()!
if _ := p.pack_text_indef() {
assert false, 'nested indef text must be rejected'
}
}
fn test_pack_bytes_indef_rejects_self_nesting() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_bytes_indef()!
if _ := p.pack_bytes_indef() {
assert false, 'nested indef bytes must be rejected'
}
}
fn test_pack_array_indef_rejected_inside_indef_text() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_text_indef()!
if _ := p.pack_array_indef() {
assert false, 'pack_array_indef inside pack_text_indef must be rejected'
}
}
fn test_pack_map_indef_rejected_inside_indef_text() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_text_indef()!
if _ := p.pack_map_indef() {
assert false, 'pack_map_indef inside pack_text_indef must be rejected'
}
}
fn test_pack_array_indef_rejected_inside_indef_bytes() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_bytes_indef()!
if _ := p.pack_array_indef() {
assert false, 'pack_array_indef inside pack_bytes_indef must be rejected'
}
}
fn test_pack_text_indef_rejected_inside_indef_bytes() {
// A text-indef chunk inside a bytes-indef would carry the wrong
// major type and break decode.
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_bytes_indef()!
if _ := p.pack_text_indef() {
assert false, 'pack_text_indef inside pack_bytes_indef must be rejected'
}
}
fn test_pack_break_rejects_no_open_indef() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
if _ := p.pack_break() {
assert false, 'break with no open indef context must be rejected'
}
}
fn test_indef_array_inside_indef_array_allowed() {
// Indef containers may freely nest; only indef text/bytes restrict
// what their chunks may be (RFC 8949 §3.2.2 vs §3.2.3).
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_array_indef()!
p.pack_array_indef()!
p.pack_break()!
p.pack_break()!
assert p.bytes() == [u8(0x9f), 0x9f, 0xff, 0xff]
}
fn test_indef_text_round_trip() {
// Sanity: a well-formed indef text (definite-length text chunks
// followed by a break) round-trips through the decoder.
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_text_indef()!
p.pack_text('hel')
p.pack_text('lo')
p.pack_break()!
mut u := cbor.new_unpacker(p.bytes().clone(), cbor.DecodeOpts{})
s := u.unpack_text()!
assert s == 'hello'
}
// ---------------------------------------------------------------------
// Encoder must refuse compositions that would silently corrupt the
// stream: a Tag with no content, a RawMessage with empty data, a
// Simple value in the reserved 24..31 range.
// ---------------------------------------------------------------------
fn test_pack_value_rejects_tag_with_empty_content() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
t := cbor.Tag{
number: 42
content_box: []
}
if _ := p.pack_value(t) {
assert false, 'pack_value must reject Tag with empty content_box'
}
}
fn test_new_tag_round_trips() {
v := cbor.new_tag(99, cbor.new_uint(7))
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_value(v)!
mut u := cbor.new_unpacker(p.bytes().clone(), cbor.DecodeOpts{})
back := u.unpack_value()!
assert back is cbor.Tag
if back is cbor.Tag {
assert back.number == 99
c := back.content()
assert c is cbor.IntNum
}
}
fn test_pack_raw_rejects_empty_message() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
if _ := p.pack_raw(cbor.RawMessage{ data: [] }) {
assert false, 'pack_raw must reject empty RawMessage'
}
}
fn test_pack_value_rejects_reserved_simple() {
// Simple values 24..31 are reserved per RFC 8949 §3.3; pack_simple
// already rejects them, and pack_value must propagate rather than
// emit zero bytes.
mut p := cbor.new_packer(cbor.EncodeOpts{})
if _ := p.pack_value(cbor.Simple{ value: 25 }) {
assert false, 'reserved Simple must surface the pack_simple error'
}
}
fn test_pack_simple_rejects_assigned_range() {
// RFC 8949 §3.3 assigns 20..23 to false/true/null/undefined.
// Encoding through pack_simple would silently produce wire-equivalent
// bytes that decode back as Bool/Null/Undefined — surprising the
// caller. The dedicated typed packers must be used instead.
mut p := cbor.new_packer(cbor.EncodeOpts{})
for v in [u8(20), 21, 22, 23] {
if _ := p.pack_simple(v) {
assert false, 'pack_simple(${v}) must be rejected'
}
}
// 0..19 still inline cleanly, 32..255 still use the 1-byte form.
p.pack_simple(0)!
p.pack_simple(19)!
p.pack_simple(32)!
p.pack_simple(255)!
}
fn test_value_as_int_boundary() {
// CBOR negative ints encode `-1 - magnitude`, so magnitude exactly
// 2^63 maps to -2^63 - 1 — outside i64 range. Must return none, not
// silently saturate to i64::min (which represents -2^63).
bytes_overflow := b('3b8000000000000000') // negative magnitude=2^63
v_over := cbor.decode[cbor.Value](bytes_overflow, cbor.DecodeOpts{})!
assert v_over.as_int() == none, 'as_int must reject -2^63 - 1'
// Just below the boundary: magnitude=2^63 - 1, value=i64::min, must fit.
bytes_min := b('3b7fffffffffffffff')
v_min := cbor.decode[cbor.Value](bytes_min, cbor.DecodeOpts{})!
assert v_min.as_int()? == i64(-9223372036854775807 - 1)
// Positive boundary: magnitude=i64::max fits, magnitude=i64::max+1 doesn't.
bytes_pmax := b('1b7fffffffffffffff')
v_pmax := cbor.decode[cbor.Value](bytes_pmax, cbor.DecodeOpts{})!
assert v_pmax.as_int()? == i64(9223372036854775807)
bytes_pover := b('1b8000000000000000')
v_pover := cbor.decode[cbor.Value](bytes_pover, cbor.DecodeOpts{})!
assert v_pover.as_int() == none, 'as_int must reject magnitudes > i64::max'
// as_uint still gives the full unsigned range.
assert v_pover.as_uint()? == u64(9223372036854775808)
}
fn test_struct_decode_rejects_duplicate_keys() {
// Struct path: deny_duplicate_keys must fire on a repeated field
// name, not silently let the second value overwrite the first.
bytes := b('a26161016161 02'.replace(' ', ''))
if _ := cbor.decode[Foo](bytes, cbor.DecodeOpts{ deny_duplicate_keys: true }) {
assert false, 'struct decode must reject duplicate keys when opted in'
}
}
struct Foo {
a int
}
// ---------------------------------------------------------------------
// Canonical encoding (RFC 8949 §4.2.1) requires the preferred form
// (§4.2.2) regardless of the source's float width hint. A FloatNum
// carrying `.double` must shrink to half precision when the value
// fits, so re-encoding a Value tree in canonical mode is byte-stable.
// ---------------------------------------------------------------------
fn test_canonical_overrides_float_bits_hint() {
v := cbor.FloatNum{
value: 1.0
bits: .double
}
mut p := cbor.new_packer(cbor.EncodeOpts{ canonical: true })
p.pack_value(v)!
assert p.bytes() == [u8(0xf9), 0x3c, 0x00], 'got ${p.bytes().hex()}'
}
fn test_non_canonical_preserves_float_bits_hint() {
// Without canonical mode the hint dictates the wire width, so the
// encoder honours `.double` even when the value fits a half.
v := cbor.FloatNum{
value: 1.0
bits: .double
}
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_value(v)!
assert p.bytes()[0] == 0xfb, 'non-canonical must respect .double hint'
}
// ---------------------------------------------------------------------
// time.Time round-trip. Tag 1 (epoch) wraps an integer when the
// nanosecond component is zero, and a float for sub-second precision
// (RFC 8949 §3.4.2). The float-decode path uses math.round so values
// just below an integer second don't drop a nanosecond.
// ---------------------------------------------------------------------
fn test_time_round_trip_preserves_full_nanoseconds() {
// Bit-exact round-trip across the full nanosecond range. The
// sub-second path uses tag 0 (RFC 3339 ns string), so unlike a
// tag-1 float it doesn't lose precision past the f64 mantissa.
for ns in [int(1), 999, 1_000_000, 250_000_000, 999_999_999] {
t := time.unix_nanosecond(1_700_000_000, ns)
bytes := cbor.encode[time.Time](t, cbor.EncodeOpts{})!
back := cbor.decode[time.Time](bytes, cbor.DecodeOpts{})!
assert back.unix() == t.unix(), 'ns=${ns}: unix drift'
assert back.nanosecond == ns, 'ns=${ns}: got ${back.nanosecond}'
}
}
fn test_time_whole_seconds_use_int_tag() {
// Whole-second values stay on the integer encoding (smaller wire,
// canonical form).
t := time.unix(1_700_000_000)
bytes := cbor.encode[time.Time](t, cbor.EncodeOpts{})!
mut u := cbor.new_unpacker(bytes, cbor.DecodeOpts{})
v := u.unpack_value()!
assert v is cbor.Tag
if v is cbor.Tag {
c := v.content()
assert c is cbor.IntNum, 'whole-seconds path must stay integer'
}
}
fn test_time_decode_rejects_nan_in_tag1_float() {
// c1 fb 7ff8000000000001 = tag 1 + qNaN. Casting NaN to i64 is
// undefined per C ABI (V's underlying), and NaN as a timestamp is
// nonsense — must reject rather than silently decode to epoch 0.
bytes := b('c1fb7ff8000000000001')
if _ := cbor.decode[time.Time](bytes, cbor.DecodeOpts{}) {
assert false, 'tag 1 NaN must be rejected'
}
}
fn test_time_decode_rejects_inf_in_tag1_float() {
bytes := b('c1fb7ff0000000000000') // +Inf
if _ := cbor.decode[time.Time](bytes, cbor.DecodeOpts{}) {
assert false, 'tag 1 +Inf must be rejected'
}
}
fn test_time_decode_rejects_overflow_in_tag1_float() {
// 1e30 — far beyond what i64 epoch seconds can hold; must error
// rather than saturate silently.
bytes := b('c1fb46293e5939a08cea')
if _ := cbor.decode[time.Time](bytes, cbor.DecodeOpts{}) {
assert false, 'tag 1 oversized float must be rejected'
}
}
fn test_time_decode_rounds_nanoseconds() {
// tag 1 + float64(1.999999999) — must round up to 1s 999_999_999ns
// rather than truncating to 1s 999_999_998 (or worse, 0).
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(1)
p.pack_float64(f64(1.999999999))
bytes := p.bytes().clone()
t := cbor.decode[time.Time](bytes, cbor.DecodeOpts{})!
assert t.unix() == 1
assert t.nanosecond >= 999_000_000, 'ns under-rounded: ${t.nanosecond}'
}
// ---------------------------------------------------------------------
// Opt-in UTF-8 validation at the encode boundary. The streaming
// pack_text trusts its input for performance, but encode[T] honours
// EncodeOpts.validate_utf8 so callers building strings from raw bytes
// can refuse to emit a payload the strict-by-default decoder would
// reject on the way back.
// ---------------------------------------------------------------------
fn test_encode_string_rejects_invalid_utf8_when_opted_in() {
// Lone continuation byte 0x80 — invalid UTF-8.
bad := unsafe { tos([u8(0x80)].data, 1) }
if _ := cbor.encode[string](bad, cbor.EncodeOpts{ validate_utf8: true }) {
assert false, 'expected validate_utf8 to reject invalid string'
}
}
fn test_encode_string_passes_valid_utf8_with_validation() {
got := cbor.encode[string]('héllo', cbor.EncodeOpts{ validate_utf8: true })!
// `héllo` = 68 c3 a9 6c 6c 6f → header 66 (text len 6) + 6 bytes.
assert got.len == 7
assert got[0] == 0x66
}
fn test_encode_string_passes_invalid_utf8_when_validation_off() {
// Default opts: encoder doesn't validate. The decoder will catch
// the invalid sequence on decode (caller responsibility).
bad := unsafe { tos([u8(0x80)].data, 1) }
got := cbor.encode[string](bad, cbor.EncodeOpts{})!
assert got.len == 2 // header 0x61 + 0x80
}
// ---------------------------------------------------------------------
// Sanity: well-formed pack_value calls (the common case) keep
// type-checking after the signature changes that the strictness
// tests above depend on.
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// `decode[T]` rejects extra bytes after the top-level item by default
// so callers can't be tricked into accepting smuggled suffixes
// (concatenated items, leftover transport framing). Callers that
// genuinely want partial parsing opt in via allow_trailing_bytes.
// ---------------------------------------------------------------------
fn test_decode_rejects_trailing_bytes() {
// `01 02` = uint(1) followed by uint(2). The second item must
// surface as an error, not be silently dropped.
bytes := b('0102')
if _ := cbor.decode[u64](bytes, cbor.DecodeOpts{}) {
assert false, 'expected trailing-byte rejection'
}
}
fn test_decode_allows_trailing_bytes_when_opted_in() {
bytes := b('0102')
v := cbor.decode[u64](bytes, cbor.DecodeOpts{ allow_trailing_bytes: true })!
assert v == 1, 'expected first item, got ${v}'
}
// ---------------------------------------------------------------------
// `decode_from` distinguishes io.Eof (legitimate end of stream) from
// transport errors. A truncated payload whose prefix happens to be a
// valid CBOR item must surface the underlying read failure rather than
// silently return a partial decode.
// ---------------------------------------------------------------------
struct FailingReader {
mut:
emitted []u8
pos int
fail_at int
}
fn (mut r FailingReader) read(mut buf []u8) !int {
if r.pos >= r.fail_at {
return error('FailingReader: simulated transport error')
}
if r.pos >= r.emitted.len {
return io.Eof{}
}
n_max := if r.fail_at - r.pos < buf.len { r.fail_at - r.pos } else { buf.len }
n := if r.emitted.len - r.pos < n_max { r.emitted.len - r.pos } else { n_max }
for i in 0 .. n {
buf[i] = r.emitted[r.pos + i]
}
r.pos += n
return n
}
fn test_decode_from_propagates_reader_error() {
// Emit `01 02` as the first chunk, then fail. Without the io.Eof
// distinction the loop would `break` and decode `[1, 2]` as a
// successful 2-item garbage; we want the read error to surface.
mut r := FailingReader{
emitted: [u8(0x01), 0x02]
fail_at: 2
}
if _ := cbor.decode_from[u64](mut r, cbor.DecodeOpts{ max_stream_bytes: 1024 }) {
assert false, 'expected reader error to propagate'
}
}
struct EofReader {
mut:
emitted []u8
pos int
}
fn (mut r EofReader) read(mut buf []u8) !int {
if r.pos >= r.emitted.len {
return io.Eof{}
}
n := if r.emitted.len - r.pos < buf.len { r.emitted.len - r.pos } else { buf.len }
for i in 0 .. n {
buf[i] = r.emitted[r.pos + i]
}
r.pos += n
return n
}
fn test_decode_from_unbounded_propagates_reader_error() {
// Same contract on the unbounded branch (no max_stream_bytes set):
// transport errors must surface, not be swallowed as a clean EOF.
mut r := FailingReader{
emitted: [u8(0x01)]
fail_at: 1
}
if v := cbor.decode_from[u64](mut r, cbor.DecodeOpts{}) {
assert false, 'expected reader error to propagate, got ${v}'
}
}
fn test_decode_from_treats_eof_as_normal() {
// Same shape as above but the reader returns io.Eof cleanly after
// emitting one valid item — must decode without error.
mut r := EofReader{
emitted: [u8(0x01)]
}
v := cbor.decode_from[u64](mut r, cbor.DecodeOpts{ max_stream_bytes: 1024 })!
assert v == 1
}
// ---------------------------------------------------------------------
// User-defined `Marshaler` output is validated before splicing into
// the parent stream. Without this check, a buggy or hostile to_cbor()
// returning a truncated/malformed item silently corrupts the
// surrounding fields (the next field gets parsed as the bad item's
// claimed payload).
// ---------------------------------------------------------------------
struct BadMarshaler {}
pub fn (b BadMarshaler) to_cbor() []u8 {
return [u8(0x78), 0x64] // text-string head claiming 100 bytes, no payload
}
struct EnvelopeWithBad {
id int
bad BadMarshaler
other int
}
fn test_marshaler_output_validated() {
e := EnvelopeWithBad{
id: 1
bad: BadMarshaler{}
other: 42
}
if _ := cbor.encode[EnvelopeWithBad](e, cbor.EncodeOpts{}) {
assert false, 'malformed Marshaler output must be rejected'
}
}
struct TrailingMarshaler {}
pub fn (t TrailingMarshaler) to_cbor() []u8 {
return [u8(0x01), 0x02] // two valid items where one was promised
}
struct EnvelopeWithTrailing {
id int
bad TrailingMarshaler
}
fn test_marshaler_rejects_trailing_bytes() {
e := EnvelopeWithTrailing{
id: 1
bad: TrailingMarshaler{}
}
if _ := cbor.encode[EnvelopeWithTrailing](e, cbor.EncodeOpts{}) {
assert false, 'Marshaler returning > 1 item must be rejected'
}
}
// ---------------------------------------------------------------------
// `pack_to` refuses to ship a buffer with an open indef container.
// `bytes()` stays low-level and doesn't validate (callers can use
// `is_complete()` to check before reading the buffer directly).
// ---------------------------------------------------------------------
struct DropWriter {
mut:
buf []u8
}
fn (mut w DropWriter) write(buf []u8) !int {
w.buf << buf
return buf.len
}
fn test_pack_to_rejects_open_indef() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_array_indef()!
p.pack_int(1)
mut w := DropWriter{}
if _ := p.pack_to(mut w) {
assert false, 'pack_to must reject buffer with open indef array'
}
}
fn test_is_complete_reports_state() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
assert p.is_complete()
p.pack_array_indef()!
assert !p.is_complete()
p.pack_int(1)
p.pack_break()!
assert p.is_complete()
}
// ---------------------------------------------------------------------
// Self-describe wrapping (RFC 8949 §3.4.6, tag 55799) is stripped
// transparently by `decode[T]` so payloads encoded with
// `EncodeOpts.self_describe` round-trip through the typed decoder.
// ---------------------------------------------------------------------
fn test_self_describe_round_trips_via_typed_decode() {
bytes := cbor.encode[u64](42, cbor.EncodeOpts{ self_describe: true })!
v := cbor.decode[u64](bytes, cbor.DecodeOpts{})!
assert v == 42
}
fn test_self_describe_visible_via_unpacker() {
// Callers that want to see the wrapper as a Tag can drive the
// Unpacker directly — `unpack_value` does not strip the marker.
bytes := cbor.encode[u64](42, cbor.EncodeOpts{ self_describe: true })!
mut u := cbor.new_unpacker(bytes, cbor.DecodeOpts{})
v := u.unpack_value()!
if v is cbor.Tag {
assert v.number == 55799
} else {
assert false, 'expected Tag(55799), got ${v.type_name()}'
}
}
// ---------------------------------------------------------------------
// `unpack_uint` and `unpack_tag` roll back position on partial-arg
// failure, matching the contract of the other typed reads.
// ---------------------------------------------------------------------
fn test_unpack_uint_rollback_on_truncated_arg() {
// 0x1b = uint(8-byte arg), no trailing payload — read_arg fails.
mut u := cbor.new_unpacker([u8(0x1b)], cbor.DecodeOpts{ allow_trailing_bytes: true })
if _ := u.unpack_uint() {
assert false, 'expected truncated arg to error'
}
assert u.pos == 0, 'expected rollback to start, got pos=${u.pos}'
}
fn test_unpack_tag_rollback_on_truncated_arg() {
mut u := cbor.new_unpacker([u8(0xdb)], cbor.DecodeOpts{ allow_trailing_bytes: true })
if _ := u.unpack_tag() {
assert false, 'expected truncated arg to error'
}
assert u.pos == 0, 'expected rollback to start, got pos=${u.pos}'
}
fn test_pack_value_well_formed() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_value(cbor.new_uint(1))!
p.pack_value(cbor.new_text('x'))!
assert p.bytes().len > 0
}
// ---------------------------------------------------------------------
// validate_utf8 must propagate into the canonical sub-encoders: the
// map / struct / Value-Map paths build temporary packers when sorting
// keys and used to drop the caller's option, silently letting invalid
// UTF-8 onto the wire even when strict-encode was requested.
// ---------------------------------------------------------------------
struct BadStrField {
good string
bad string
}
fn test_validate_utf8_propagates_into_canonical_struct() {
v := BadStrField{
good: 'ok'
bad: unsafe { tos(c'\xff\xfe', 2) }
}
if _ := cbor.encode[BadStrField](v, cbor.EncodeOpts{
canonical: true
validate_utf8: true
})
{
assert false, 'canonical encode must reject invalid UTF-8 when validate_utf8 is set'
}
}
fn test_validate_utf8_propagates_into_canonical_map() {
mut m := map[string]string{}
m['a'] = 'ok'
m['b'] = unsafe { tos(c'\xff\xfe', 2) }
if _ := cbor.encode[map[string]string](m, cbor.EncodeOpts{
canonical: true
validate_utf8: true
})
{
assert false, 'canonical encode must reject invalid UTF-8 in map values'
}
}
fn test_validate_utf8_off_allows_invalid_in_canonical() {
v := BadStrField{
good: 'ok'
bad: unsafe { tos(c'\xff\xfe', 2) }
}
// Default opts (validate_utf8: false) must still let the bytes through
// in canonical mode — the caller opted out of validation.
bytes := cbor.encode[BadStrField](v, cbor.EncodeOpts{ canonical: true })!
assert bytes.len > 0
}
// ---------------------------------------------------------------------
// SWAR ASCII fast path in `utf8_validate_slice` must remain correct on
// payloads of any length and any starting offset (the load is now via
// memcpy to be safe on strict-alignment targets — but the result must
// stay byte-for-byte identical with the per-byte path).
// ---------------------------------------------------------------------
fn test_validate_utf8_long_ascii_run() {
// 257 bytes: spans multiple 8-byte SWAR chunks plus a non-multiple tail.
s := 'a'.repeat(257)
bytes := cbor.encode[string](s, cbor.EncodeOpts{ validate_utf8: true })!
back := cbor.decode[string](bytes, cbor.DecodeOpts{ validate_utf8: true })!
assert back == s
}
fn test_validate_utf8_non_ascii_after_swar_chunks() {
// 16 ASCII bytes (two SWAR chunks) followed by a 2-byte UTF-8 rune,
// then more ASCII. Confirms the fast path bails out cleanly into the
// per-byte decoder when the high bit appears.
s := 'aaaaaaaa' + 'aaaaaaaa' + 'é' + 'bbbb'
bytes := cbor.encode[string](s, cbor.EncodeOpts{ validate_utf8: true })!
back := cbor.decode[string](bytes, cbor.DecodeOpts{ validate_utf8: true })!
assert back == s
}
@@ -0,0 +1,781 @@
// RFC 8949 Appendix A conformance tests. Each entry is taken verbatim
// from the published `cbor/test-vectors` list. Roundtrip-true entries
// are exercised in both directions (encode-and-compare-bytes plus
// decode-and-compare-value); roundtrip-false entries are decode-only,
// which matches the test-vector flag.
//
// Hex bytes are kept as string literals so the diffs against the RFC
// are obvious when reading the file.
module main
import encoding.cbor
import encoding.hex
import math
fn h(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
fn bytes_eq(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}
fn assert_encode_uint(v u64, hex_expected string) {
got := cbor.encode[u64](v, cbor.EncodeOpts{}) or { panic(err) }
expected := h(hex_expected)
if !bytes_eq(got, expected) {
panic('encode u64 ${v}: got ${hex.encode(got)}, want ${hex_expected}')
}
}
fn assert_encode_int(v i64, hex_expected string) {
got := cbor.encode[i64](v, cbor.EncodeOpts{}) or { panic(err) }
expected := h(hex_expected)
if !bytes_eq(got, expected) {
panic('encode i64 ${v}: got ${hex.encode(got)}, want ${hex_expected}')
}
}
// ---------------------------------------------------------------------
// Unsigned integers (major type 0)
// ---------------------------------------------------------------------
fn test_unsigned_zero() {
assert_encode_uint(0, '00')
}
fn test_unsigned_small() {
assert_encode_uint(1, '01')
assert_encode_uint(10, '0a')
assert_encode_uint(23, '17')
}
fn test_unsigned_one_byte() {
assert_encode_uint(24, '1818')
assert_encode_uint(25, '1819')
assert_encode_uint(100, '1864')
assert_encode_uint(0xff, '18ff')
}
fn test_unsigned_two_bytes() {
assert_encode_uint(1000, '1903e8')
assert_encode_uint(0xffff, '19ffff')
}
fn test_unsigned_four_bytes() {
assert_encode_uint(1_000_000, '1a000f4240')
assert_encode_uint(0xffffffff, '1affffffff')
}
fn test_unsigned_eight_bytes() {
assert_encode_uint(1_000_000_000_000, '1b000000e8d4a51000')
assert_encode_uint(u64(0xffffffffffffffff), '1bffffffffffffffff')
}
// ---------------------------------------------------------------------
// Negative integers (major type 1)
// ---------------------------------------------------------------------
fn test_negative_small() {
assert_encode_int(-1, '20')
assert_encode_int(-10, '29')
}
fn test_negative_one_byte() {
assert_encode_int(-100, '3863')
}
fn test_negative_two_bytes() {
assert_encode_int(-1000, '3903e7')
}
fn test_negative_extreme() {
// -2^63 still fits i64.
assert_encode_int(-9_223_372_036_854_775_808, '3b7fffffffffffffff')
}
fn test_negative_lower_bound_via_packer() {
// -2^64 (lower bound of CBOR negative ints) requires the full u64
// argument and can't be represented as an i64.
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_negative_arg(u64(0xffffffffffffffff))
assert bytes_eq(p.bytes(), h('3bffffffffffffffff'))
}
// ---------------------------------------------------------------------
// Floats — preferred serialisation (RFC 8949 §4.2.2)
// ---------------------------------------------------------------------
fn assert_encode_float(v f64, hex_expected string) {
got := cbor.encode[f64](v, cbor.EncodeOpts{}) or { panic(err) }
expected := h(hex_expected)
if !bytes_eq(got, expected) {
panic('encode f64 ${v}: got ${hex.encode(got)}, want ${hex_expected}')
}
}
fn test_float_zero() {
assert_encode_float(0.0, 'f90000')
// -0.0 — distinct bit pattern from +0.0.
neg_zero := math.f64_from_bits(u64(0x8000000000000000))
assert_encode_float(neg_zero, 'f98000')
}
fn test_float_simple_values() {
assert_encode_float(1.0, 'f93c00')
assert_encode_float(1.5, 'f93e00')
assert_encode_float(65504.0, 'f97bff')
assert_encode_float(-4.0, 'f9c400')
}
fn test_float_subnormal_half() {
// 2^-24 — smallest positive half subnormal (preserved exactly in f32/f64).
assert_encode_float(5.960464477539063e-08, 'f90001')
// 2^-14 — smallest positive normal half.
assert_encode_float(6.103515625e-05, 'f90400')
}
fn test_float_single() {
assert_encode_float(100000.0, 'fa47c35000')
assert_encode_float(3.4028234663852886e+38, 'fa7f7fffff')
}
fn test_float_double() {
assert_encode_float(1.1, 'fb3ff199999999999a')
assert_encode_float(1.0e+300, 'fb7e37e43c8800759c')
assert_encode_float(-4.1, 'fbc010666666666666')
}
fn test_float_special_values() {
assert_encode_float(math.inf(1), 'f97c00')
assert_encode_float(math.inf(-1), 'f9fc00')
// NaN — encoder collapses to the canonical quiet NaN.
got := cbor.encode[f64](math.nan(), cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('f97e00')), 'NaN encoded as ${hex.encode(got)}, want f97e00'
}
// ---------------------------------------------------------------------
// Booleans, null, undefined
// ---------------------------------------------------------------------
fn test_bool_false() {
got := cbor.encode[bool](false, cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, [u8(0xf4)])
}
fn test_bool_true() {
got := cbor.encode[bool](true, cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, [u8(0xf5)])
}
fn test_null_via_packer() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_null()
assert bytes_eq(p.bytes(), [u8(0xf6)])
}
fn test_undefined_via_packer() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_undefined()
assert bytes_eq(p.bytes(), [u8(0xf7)])
}
fn test_simple_values() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_simple(16) or { panic(err) }
assert bytes_eq(p.bytes(), h('f0'))
mut p3 := cbor.new_packer(cbor.EncodeOpts{})
p3.pack_simple(255) or { panic(err) }
assert bytes_eq(p3.bytes(), h('f8ff'))
}
// RFC 8949 §3.3: encoder MUST NOT issue two-byte sequences starting
// with 0xf8 and continuing with a byte < 0x20. We refuse to emit such a
// value, and the decoder rejects them on input.
fn test_simple_values_rfc8949_strictness() {
// Encoding side: pack_simple(24..31) returns an error.
mut p := cbor.new_packer(cbor.EncodeOpts{})
if _ := p.pack_simple(24) {
assert false, 'expected pack_simple(24) to fail'
}
if _ := p.pack_simple(31) {
assert false, 'expected pack_simple(31) to fail'
}
// Decoder side: f818 (simple(24) two-byte form) is malformed.
cbor.decode[cbor.Value](h('f818'), cbor.DecodeOpts{}) or {
assert err.msg().contains('1-byte form'), 'unexpected: ${err.msg()}'
return
}
assert false, 'expected decoder to reject simple(24) two-byte form'
}
// ---------------------------------------------------------------------
// Byte and text strings
// ---------------------------------------------------------------------
fn test_empty_byte_string() {
got := cbor.encode[[]u8]([]u8{}, cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('40'))
}
fn test_byte_string_4() {
got := cbor.encode[[]u8]([u8(0x01), 0x02, 0x03, 0x04], cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('4401020304'))
}
fn test_empty_text_string() {
got := cbor.encode[string]('', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('60'))
}
fn test_text_a() {
got := cbor.encode[string]('a', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('6161'))
}
fn test_text_ietf() {
got := cbor.encode[string]('IETF', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('6449455446'))
}
fn test_text_escaped() {
got := cbor.encode[string]('"\\', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('62225c'))
}
fn test_text_utf8_2byte() {
got := cbor.encode[string]('ü', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('62c3bc'))
}
fn test_text_utf8_3byte() {
got := cbor.encode[string]('水', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('63e6b0b4'))
}
fn test_text_utf8_4byte() {
got := cbor.encode[string]('𐅑', cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('64f0908591'))
}
// ---------------------------------------------------------------------
// Arrays
// ---------------------------------------------------------------------
fn test_empty_array() {
got := cbor.encode[[]int]([]int{}, cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('80'))
}
fn test_array_3() {
got := cbor.encode[[]int]([1, 2, 3], cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('83010203'))
}
fn test_array_nested_via_value() {
v := cbor.Value(cbor.Array{
elements: [
cbor.Value(cbor.new_uint(1)),
cbor.Value(cbor.Array{
elements: [cbor.Value(cbor.new_uint(2)), cbor.Value(cbor.new_uint(3))]
}),
cbor.Value(cbor.Array{
elements: [cbor.Value(cbor.new_uint(4)), cbor.Value(cbor.new_uint(5))]
}),
]
})
got := cbor.encode_value(v, cbor.EncodeOpts{})!
assert bytes_eq(got, h('8301820203820405'))
}
fn test_array_25_items() {
mut elements := []u64{cap: 25}
for i in 0 .. 25 {
elements << u64(i + 1)
}
got := cbor.encode[[]u64](elements, cbor.EncodeOpts{}) or { panic(err) }
want := '98190102030405060708090a0b0c0d0e0f101112131415161718181819'
assert bytes_eq(got, h(want))
}
// ---------------------------------------------------------------------
// Maps
// ---------------------------------------------------------------------
fn test_empty_map() {
got := cbor.encode[map[string]int](map[string]int{}, cbor.EncodeOpts{}) or { panic(err) }
assert bytes_eq(got, h('a0'))
}
fn test_int_key_map_via_packer() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_map_header(2)
p.pack_int(1)
p.pack_int(2)
p.pack_int(3)
p.pack_int(4)
assert bytes_eq(p.bytes(), h('a201020304'))
}
fn test_string_key_map_with_array_value() {
v := cbor.Value(cbor.Map{
pairs: [
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'a'
})
value: cbor.Value(cbor.new_uint(1))
},
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'b'
})
value: cbor.Value(cbor.Array{
elements: [cbor.Value(cbor.new_uint(2)), cbor.Value(cbor.new_uint(3))]
})
},
]
})
got := cbor.encode_value(v, cbor.EncodeOpts{})!
assert bytes_eq(got, h('a26161016162820203'))
}
fn test_array_with_map_inside() {
v := cbor.Value(cbor.Array{
elements: [
cbor.Value(cbor.Text{
value: 'a'
}),
cbor.Value(cbor.Map{
pairs: [
cbor.MapPair{
key: cbor.Value(cbor.Text{
value: 'b'
})
value: cbor.Value(cbor.Text{
value: 'c'
})
},
]
}),
]
})
got := cbor.encode_value(v, cbor.EncodeOpts{})!
assert bytes_eq(got, h('826161a161626163'))
}
// ---------------------------------------------------------------------
// Tags
// ---------------------------------------------------------------------
fn test_tag_date_time() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(0)
p.pack_text('2013-03-21T20:04:00Z')
assert bytes_eq(p.bytes(), h('c074323031332d30332d32315432303a30343a30305a'))
}
fn test_tag_epoch_int() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(1)
p.pack_int(1363896240)
assert bytes_eq(p.bytes(), h('c11a514b67b0'))
}
fn test_tag_epoch_float() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(1)
p.pack_float64(1363896240.5)
assert bytes_eq(p.bytes(), h('c1fb41d452d9ec200000'))
}
fn test_tag_uri() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(32)
p.pack_text('http://www.example.com')
assert bytes_eq(p.bytes(), h('d82076687474703a2f2f7777772e6578616d706c652e636f6d'))
}
fn test_tag_unsigned_bignum() {
// Tag 2 + 9-byte big-endian magnitude for 18446744073709551616 = 2^64.
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_tag(2)
p.pack_bytes([u8(0x01), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
assert bytes_eq(p.bytes(), h('c249010000000000000000'))
}
// ---------------------------------------------------------------------
// Indefinite-length items
// ---------------------------------------------------------------------
fn test_indefinite_byte_string() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_bytes_indef()!
p.pack_bytes([u8(0x01), 0x02])
p.pack_bytes([u8(0x03), 0x04, 0x05])
p.pack_break()!
assert bytes_eq(p.bytes(), h('5f42010243030405ff'))
}
fn test_indefinite_text_string() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_text_indef()!
p.pack_text('strea')
p.pack_text('ming')
p.pack_break()!
assert bytes_eq(p.bytes(), h('7f657374726561646d696e67ff'))
}
fn test_indefinite_array() {
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_array_indef()!
p.pack_break()!
assert bytes_eq(p.bytes(), h('9fff'))
}
fn test_indefinite_array_with_definite_inside() {
// [_ 1, [2, 3], [_ 4, 5]]
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_array_indef()!
p.pack_int(1)
p.pack_array_header(2)
p.pack_int(2)
p.pack_int(3)
p.pack_array_indef()!
p.pack_int(4)
p.pack_int(5)
p.pack_break()!
p.pack_break()!
assert bytes_eq(p.bytes(), h('9f018202039f0405ffff'))
}
fn test_indefinite_map() {
// {_ "a": 1, "b": [_ 2, 3]}
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_map_indef()!
p.pack_text('a')
p.pack_int(1)
p.pack_text('b')
p.pack_array_indef()!
p.pack_int(2)
p.pack_int(3)
p.pack_break()!
p.pack_break()!
assert bytes_eq(p.bytes(), h('bf61610161629f0203ffff'))
}
// ---------------------------------------------------------------------
// Decode side: every Appendix A vector decodes to the right value
// ---------------------------------------------------------------------
struct UintCase {
hex string
val u64
}
struct IntCase {
hex string
val i64
}
fn test_decode_unsigned() {
for pair in [
UintCase{
hex: '00'
val: 0
},
UintCase{
hex: '17'
val: 23
},
UintCase{
hex: '1818'
val: 24
},
UintCase{
hex: '1864'
val: 100
},
UintCase{
hex: '1903e8'
val: 1000
},
UintCase{
hex: '1a000f4240'
val: 1_000_000
},
UintCase{
hex: '1b000000e8d4a51000'
val: 1_000_000_000_000
},
UintCase{
hex: '1bffffffffffffffff'
val: u64(0xffffffffffffffff)
},
] {
v := cbor.decode[u64](h(pair.hex), cbor.DecodeOpts{}) or {
panic('decode ${pair.hex}: ${err}')
}
assert v == pair.val, 'decoded ${pair.hex}: got ${v}, want ${pair.val}'
}
}
fn test_decode_negative() {
for pair in [
IntCase{
hex: '20'
val: -1
},
IntCase{
hex: '29'
val: -10
},
IntCase{
hex: '3863'
val: -100
},
IntCase{
hex: '3903e7'
val: -1000
},
IntCase{
hex: '3b7fffffffffffffff'
val: i64(-9_223_372_036_854_775_808)
},
] {
v := cbor.decode[i64](h(pair.hex), cbor.DecodeOpts{}) or {
panic('decode ${pair.hex}: ${err}')
}
assert v == pair.val, 'decoded ${pair.hex}: got ${v}, want ${pair.val}'
}
}
fn test_decode_floats() {
// Half precision.
v := cbor.decode[f64](h('f93c00'), cbor.DecodeOpts{}) or { panic(err) }
assert v == 1.0
v2 := cbor.decode[f64](h('f93e00'), cbor.DecodeOpts{}) or { panic(err) }
assert v2 == 1.5
v3 := cbor.decode[f64](h('f97bff'), cbor.DecodeOpts{}) or { panic(err) }
assert v3 == 65504.0
// Subnormal half.
v4 := cbor.decode[f64](h('f90001'), cbor.DecodeOpts{}) or { panic(err) }
assert v4 == 5.960464477539063e-08, 'subnormal half: ${v4}'
// f32 / f64.
v5 := cbor.decode[f64](h('fa47c35000'), cbor.DecodeOpts{}) or { panic(err) }
assert v5 == 100000.0
v6 := cbor.decode[f64](h('fb3ff199999999999a'), cbor.DecodeOpts{}) or { panic(err) }
assert v6 == 1.1
// Inf / -Inf.
v7 := cbor.decode[f64](h('f97c00'), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_inf(v7, 1)
v8 := cbor.decode[f64](h('f9fc00'), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_inf(v8, -1)
// NaN.
v9 := cbor.decode[f64](h('f97e00'), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_nan(v9)
// Alternate non-canonical encodings (roundtrip=false but must decode).
for hex_str in ['fa7f800000', 'fb7ff0000000000000'] {
val := cbor.decode[f64](h(hex_str), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_inf(val, 1), '${hex_str} → ${val}'
}
for hex_str in ['faff800000', 'fbfff0000000000000'] {
val := cbor.decode[f64](h(hex_str), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_inf(val, -1), '${hex_str} → ${val}'
}
for hex_str in ['fa7fc00000', 'fb7ff8000000000000'] {
val := cbor.decode[f64](h(hex_str), cbor.DecodeOpts{}) or { panic(err) }
assert math.is_nan(val), '${hex_str} → ${val}'
}
}
fn test_decode_bool_null() {
bf := cbor.decode[bool](h('f4'), cbor.DecodeOpts{}) or { panic(err) }
assert bf == false
bt := cbor.decode[bool](h('f5'), cbor.DecodeOpts{}) or { panic(err) }
assert bt == true
v := cbor.decode[cbor.Value](h('f6'), cbor.DecodeOpts{}) or { panic(err) }
assert v.is_nil()
u := cbor.decode[cbor.Value](h('f7'), cbor.DecodeOpts{}) or { panic(err) }
assert u.is_undefined()
}
fn test_decode_simple_extended() {
// Inline form for simple(16).
v0 := cbor.decode[cbor.Value](h('f0'), cbor.DecodeOpts{}) or { panic(err) }
assert v0 is cbor.Simple
if v0 is cbor.Simple {
assert v0.value == 16
}
// 1-byte form for simple(32) — first valid two-byte simple value.
mut p := cbor.new_packer(cbor.EncodeOpts{})
p.pack_simple(32) or { panic(err) }
v32 := cbor.decode[cbor.Value](p.bytes(), cbor.DecodeOpts{}) or { panic(err) }
assert v32 is cbor.Simple
if v32 is cbor.Simple {
assert v32.value == 32
}
// Top of the range.
v2 := cbor.decode[cbor.Value](h('f8ff'), cbor.DecodeOpts{}) or { panic(err) }
if v2 is cbor.Simple {
assert v2.value == 255
}
}
fn test_decode_strings() {
a := cbor.decode[string](h('60'), cbor.DecodeOpts{}) or { panic(err) }
assert a == ''
b := cbor.decode[string](h('6161'), cbor.DecodeOpts{}) or { panic(err) }
assert b == 'a'
c := cbor.decode[string](h('6449455446'), cbor.DecodeOpts{}) or { panic(err) }
assert c == 'IETF'
d := cbor.decode[string](h('62c3bc'), cbor.DecodeOpts{}) or { panic(err) }
assert d == 'ü'
e := cbor.decode[string](h('63e6b0b4'), cbor.DecodeOpts{}) or { panic(err) }
assert e == '水'
f := cbor.decode[string](h('64f0908591'), cbor.DecodeOpts{}) or { panic(err) }
assert f == '𐅑'
}
fn test_decode_array() {
v := cbor.decode[[]int](h('83010203'), cbor.DecodeOpts{}) or { panic(err) }
assert v == [1, 2, 3]
}
fn test_decode_map() {
v := cbor.decode[map[string]int](h('a26161016162820203'), cbor.DecodeOpts{}) or {
// This vector has b → [2,3], not int — so this decode should fail.
assert err.msg().contains('mismatch') || err.msg().contains('overflow')
return
}
_ = v
assert false, 'expected type mismatch on map[string]int decode of array value'
}
fn test_decode_indefinite_text() {
// "stream"+"ing"
v := cbor.decode[string](h('7f657374726561646d696e67ff'), cbor.DecodeOpts{}) or { panic(err) }
assert v == 'streaming'
}
fn test_decode_indefinite_array() {
v := cbor.decode[[]int](h('9fff'), cbor.DecodeOpts{}) or { panic(err) }
assert v == []int{}
v2 := cbor.decode[[]int](h('9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff'), cbor.DecodeOpts{}) or {
panic(err)
}
mut want := []int{cap: 25}
for i in 0 .. 25 {
want << i + 1
}
assert v2 == want
}
fn test_decode_indefinite_bytes() {
v := cbor.decode[[]u8](h('5f42010243030405ff'), cbor.DecodeOpts{}) or { panic(err) }
assert bytes_eq(v, [u8(0x01), 0x02, 0x03, 0x04, 0x05])
}
// ---------------------------------------------------------------------
// Round-trip for every roundtrip=true vector via the Value tree
// ---------------------------------------------------------------------
const roundtrip_vectors = [
'00',
'01',
'0a',
'17',
'1818',
'1819',
'1864',
'1903e8',
'1a000f4240',
'1b000000e8d4a51000',
'1bffffffffffffffff',
'3bffffffffffffffff',
'20',
'29',
'3863',
'3903e7',
'f90000',
'f98000',
'f93c00',
'fb3ff199999999999a',
'f93e00',
'f97bff',
'fa47c35000',
'fa7f7fffff',
'fb7e37e43c8800759c',
'f90001',
'f90400',
'f9c400',
'fbc010666666666666',
'f97c00',
'f97e00',
'f9fc00',
'f4',
'f5',
'f6',
'f7',
'f0',
'f8ff',
'40',
'4401020304',
'60',
'6161',
'6449455446',
'62225c',
'62c3bc',
'63e6b0b4',
'64f0908591',
'80',
'83010203',
'8301820203820405',
'98190102030405060708090a0b0c0d0e0f101112131415161718181819',
'a0',
'a201020304',
'a26161016162820203',
'826161a161626163',
'a56161614161626142616361436164614461656145',
]
fn test_value_roundtrip_all_canonical_vectors() {
for hex_str in roundtrip_vectors {
input := h(hex_str)
decoded := cbor.decode[cbor.Value](input, cbor.DecodeOpts{}) or {
panic('decode ${hex_str}: ${err}')
}
got := cbor.encode_value(decoded, cbor.EncodeOpts{})!
assert bytes_eq(got, input), 'roundtrip ${hex_str}: got ${hex.encode(got)}'
}
}
// Tag-bearing vectors round-trip via the Value tree as well.
fn test_value_roundtrip_tag_vectors() {
for hex_str in [
'c074323031332d30332d32315432303a30343a30305a',
'c11a514b67b0',
'c1fb41d452d9ec200000',
'd74401020304',
'd818456449455446',
'd82076687474703a2f2f7777772e6578616d706c652e636f6d',
'c249010000000000000000',
'c349010000000000000000',
] {
input := h(hex_str)
decoded := cbor.decode[cbor.Value](input, cbor.DecodeOpts{}) or {
panic('decode ${hex_str}: ${err}')
}
got := cbor.encode_value(decoded, cbor.EncodeOpts{})!
assert bytes_eq(got, input), 'tag roundtrip ${hex_str}: got ${hex.encode(got)}'
}
}
@@ -0,0 +1,251 @@
// Tests that the decoder is safe against adversarial input: malformed
// initial bytes, premature EOF, depth bombs, indefinite-length nesting,
// and invalid UTF-8.
module main
import encoding.cbor
import encoding.hex
fn h(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
// ---------------------------------------------------------------------
// EOF handling
// ---------------------------------------------------------------------
fn test_eof_truncated_uint() {
// 0x18 = uint(1-byte arg), but no following byte.
if _ := cbor.decode[u64](h('18'), cbor.DecodeOpts{}) {
assert false, 'expected EOF error'
}
}
fn test_eof_truncated_array() {
// 0x83 = array of 3, but only 1 element follows.
if _ := cbor.decode[[]int](h('8301'), cbor.DecodeOpts{}) {
assert false, 'expected EOF error in array'
}
}
fn test_eof_truncated_string() {
// 0x65 = text len 5, but only 3 bytes follow.
if _ := cbor.decode[string](h('656162'), cbor.DecodeOpts{}) {
assert false, 'expected EOF error in text'
}
}
// ---------------------------------------------------------------------
// Reserved additional info
// ---------------------------------------------------------------------
fn test_reserved_info_rejected() {
// 0x1c = major 0, info 28 (reserved).
if _ := cbor.decode[cbor.Value](h('1c'), cbor.DecodeOpts{}) {
assert false, 'expected malformed for info 28'
}
}
// ---------------------------------------------------------------------
// Depth bomb
// ---------------------------------------------------------------------
fn test_depth_bomb_rejected() {
// Build an indefinite-length array nested 1000 deep.
mut deep := []u8{cap: 2002}
for _ in 0 .. 1000 {
deep << 0x9f // start indefinite array
}
for _ in 0 .. 1000 {
deep << 0xff // close
}
if _ := cbor.decode[cbor.Value](deep, cbor.DecodeOpts{ max_depth: 16 }) {
assert false, 'expected MaxDepthError'
}
}
// ---------------------------------------------------------------------
// Indefinite-length string with mismatched chunk
// ---------------------------------------------------------------------
fn test_indef_text_with_byte_chunk_rejected() {
// 0x7f = indef text. 0x42 = bytes(2). Should fail.
if _ := cbor.decode[string](h('7f4201020203ff'), cbor.DecodeOpts{}) {
assert false, 'expected malformed for mixed indef-text chunk'
}
}
fn test_nested_indef_text_rejected() {
// 0x7f7f...ff is indef text containing indef text — disallowed.
if _ := cbor.decode[string](h('7f7f60ffff'), cbor.DecodeOpts{}) {
assert false, 'expected malformed for nested indef text'
}
}
// ---------------------------------------------------------------------
// UTF-8 validation
// ---------------------------------------------------------------------
fn test_invalid_utf8_rejected() {
// 0x62 = text len 2, then invalid 2-byte sequence 0xc3 0x28.
if _ := cbor.decode[string](h('62c328'), cbor.DecodeOpts{}) {
assert false, 'expected InvalidUtf8Error'
}
}
fn test_invalid_utf8_can_be_disabled() {
// Same input but with validate_utf8 = false succeeds (caller
// accepts responsibility for handling raw bytes).
got := cbor.decode[string](h('62c328'), cbor.DecodeOpts{ validate_utf8: false }) or {
panic('expected success: ${err}')
}
assert got.len == 2
}
fn test_invalid_utf8_overlong_rejected() {
// "/" = 0x2f, but encoded as 2-byte overlong 0xc0 0xaf — rejected.
if _ := cbor.decode[string](h('62c0af'), cbor.DecodeOpts{}) {
assert false, 'expected InvalidUtf8Error for overlong'
}
}
fn test_invalid_utf8_surrogate_rejected() {
// U+D800 (high surrogate) in 3-byte form: 0xed 0xa0 0x80.
if _ := cbor.decode[string](h('63eda080'), cbor.DecodeOpts{}) {
assert false, 'expected InvalidUtf8Error for surrogate'
}
}
// ---------------------------------------------------------------------
// Unknown fields in struct decode
// ---------------------------------------------------------------------
struct Strict {
a int
}
fn test_unknown_field_tolerated_by_default() {
// {"a": 1, "b": 2}
bytes := h('a26161016162 02'.replace(' ', ''))
got := cbor.decode[Strict](bytes, cbor.DecodeOpts{})!
assert got.a == 1
}
fn test_unknown_field_rejected_when_opted_in() {
bytes := h('a26161016162 02'.replace(' ', ''))
if _ := cbor.decode[Strict](bytes, cbor.DecodeOpts{ deny_unknown_fields: true }) {
assert false, 'expected UnknownFieldError'
}
}
// ---------------------------------------------------------------------
// Native tag 0/1 content-type validation (RFC 8949 §3.4.1). Unlike a
// permissive decoder, we reject tag 0 wrapping non-text and tag 1
// wrapping non-numbers — same behaviour as QCBOR (the IETF reference).
// These cases come from the cbor-wg/bad conformance corpus.
// ---------------------------------------------------------------------
fn test_tag0_wrapping_map_rejected() {
// c0 a1 61 61 00 = tag(0, {"a": 0}) — tag 0 must be tstr.
if _ := cbor.decode[cbor.Value](h('c0a1616100'), cbor.DecodeOpts{}) {
assert false, 'expected tag-0 type rejection'
}
}
fn test_tag1_wrapping_map_rejected() {
// c1 a1 61 61 00 = tag(1, {"a": 0}) — tag 1 must be int or float.
if _ := cbor.decode[cbor.Value](h('c1a1616100'), cbor.DecodeOpts{}) {
assert false, 'expected tag-1 type rejection'
}
}
fn test_tag0_wrapping_text_accepted() {
// c0 74 ... = tag(0, "2013-03-21T20:04:00Z") — well-formed.
v := cbor.decode[cbor.Value](h('c074323031332d30332d32315432303a30343a30305a'), cbor.DecodeOpts{}) or {
assert false, 'tag 0 + text MUST be accepted: ${err}'
return
}
assert v is cbor.Tag
}
fn test_tag1_wrapping_int_or_float_accepted() {
v := cbor.decode[cbor.Value](h('c11a514b67b0'), cbor.DecodeOpts{}) or {
assert false, 'tag 1 + int MUST be accepted: ${err}'
return
}
assert v is cbor.Tag
v2 := cbor.decode[cbor.Value](h('c1fb41d452d9ec200000'), cbor.DecodeOpts{}) or {
assert false, 'tag 1 + float MUST be accepted: ${err}'
return
}
assert v2 is cbor.Tag
}
// ---------------------------------------------------------------------
// Header-length overflow: lengths beyond i64::max must be rejected,
// not silently wrapped to -1 (which would alias the indefinite-length
// sentinel and steer callers into the wrong loop). See decoder.v
// `unpack_array_header` / `unpack_map_header`.
// ---------------------------------------------------------------------
fn test_array_header_oversized_length_rejected() {
// 9b ff ff ff ff ff ff ff ff = array, info=27, arg=u64::max.
mut u := cbor.new_unpacker(h('9bffffffffffffffff'), cbor.DecodeOpts{})
if n := u.unpack_array_header() {
assert false, 'expected oversized length rejection, got ${n}'
}
}
fn test_map_header_oversized_length_rejected() {
// bb ff ff ff ff ff ff ff ff = map, info=27, arg=u64::max.
mut u := cbor.new_unpacker(h('bbffffffffffffffff'), cbor.DecodeOpts{})
if n := u.unpack_map_header() {
assert false, 'expected oversized length rejection, got ${n}'
}
}
fn test_array_header_at_i64_max_accepted() {
// 9b 7f ff ff ff ff ff ff ff = array, info=27, arg=i64::max — boundary.
mut u := cbor.new_unpacker(h('9b7fffffffffffffff'), cbor.DecodeOpts{})
n := u.unpack_array_header() or {
assert false, 'i64::max boundary must succeed: ${err}'
return
}
assert n == max_i64
}
// ---------------------------------------------------------------------
// skip_value MUST enforce RFC 8949 §3.2.3 chunk rules for indefinite
// strings: each chunk must be a definite-length string of the same
// major type. Otherwise the skip path silently accepts what
// unpack_text / unpack_bytes correctly reject — letting malformed
// CBOR through RawMessage / Unmarshaler / unknown-field skipping.
// ---------------------------------------------------------------------
fn test_skip_value_rejects_cross_type_indef_string_chunk() {
// 7f 41 00 ff = indef text containing one bytes chunk (major=2),
// then break. Same chunk-type rule applies to skip_value as to
// unpack_text — both reject cross-type chunks.
mut u := cbor.new_unpacker(h('7f4100ff'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject cross-type indef chunk'
}
}
fn test_skip_value_rejects_nested_indef_string_chunk() {
// 7f 7f 61 61 ff ff = indef text whose chunk is itself indefinite.
mut u := cbor.new_unpacker(h('7f7f6161ffff'), cbor.DecodeOpts{})
if _ := u.skip_value() {
assert false, 'skip_value must reject nested indef chunk'
}
}
fn test_raw_message_rejects_malformed_indef_string() {
// Same payload as above, but exercised through the RawMessage path
// (which calls skip_value internally to compute the slice bounds).
mut u := cbor.new_unpacker(h('7f4100ff'), cbor.DecodeOpts{})
if _ := u.unpack_raw() {
assert false, 'unpack_raw must reject cross-type indef chunk'
}
}
@@ -0,0 +1,8 @@
module main
import encoding.cbor
fn test_smoke_uint() {
out := cbor.encode[u64](u64(0), cbor.EncodeOpts{})!
assert out == [u8(0x00)]
}
@@ -0,0 +1,39 @@
// Tests that `time.Time` auto-tags as RFC 8949 tag 1 (epoch seconds)
// on encode and accepts both tag 0 (RFC 3339 text) and tag 1 (epoch
// seconds, integer or float) on decode.
module main
import encoding.cbor
import encoding.hex
import time
fn h(s string) []u8 {
return hex.decode(s) or { panic('invalid hex: ${s}') }
}
fn test_time_encode_tag1() {
t := time.unix(1363896240)
bytes := cbor.encode[time.Time](t, cbor.EncodeOpts{})!
// Wire form: c1 (tag 1) + 1a 51 4b 67 b0 (4-byte uint).
assert bytes == h('c11a514b67b0'), 'got ${hex.encode(bytes)}'
}
fn test_time_decode_tag1() {
got := cbor.decode[time.Time](h('c11a514b67b0'), cbor.DecodeOpts{})!
assert got.unix() == 1363896240
}
fn test_time_decode_tag0_iso8601() {
// Tag 0 + text "2013-03-21T20:04:00Z".
got :=
cbor.decode[time.Time](h('c074323031332d30332d32315432303a30343a30305a'), cbor.DecodeOpts{})!
assert got.unix() == 1363896240
}
fn test_time_decode_tag1_float() {
got := cbor.decode[time.Time](h('c1fb41d452d9ec200000'), cbor.DecodeOpts{})!
// 1363896240.5 — half-second offset.
assert got.unix() == 1363896240
// Sub-second component is non-zero (~500ms).
assert got.nanosecond > 0
}
@@ -0,0 +1,348 @@
// Third-party conformance: drives the entire `appendix_a.json` corpus
// from https://github.com/cbor/test-vectors (the same file that ciborium,
// serde_cbor and cbor2 use). Each entry is checked against:
//
// * its `hex` round-trips byte-exact when `roundtrip == true`
// * its `decoded` JSON value matches the V-decoded `cbor.Value`
// * for entries that only carry a `diagnostic` (NaN, Infinity, undefined,
// simple(N), tag(N)(...), h'…'), structural sanity is enforced via
// the diagnostic prefix.
//
// The fixture file lives next to this test so the corpus is reproducible
// and offline-buildable.
module main
import encoding.cbor
import encoding.hex
import math
import os
import x.json2
fn h(s string) []u8 {
return hex.decode(s) or { panic('bad hex ${s}') }
}
fn bytes_eq(a []u8, b []u8) bool {
if a.len != b.len {
return false
}
for i in 0 .. a.len {
if a[i] != b[i] {
return false
}
}
return true
}
// match_decoded compares a V `Value` against a parsed JSON value from the
// vector's `decoded` field. Returns an error string on mismatch; empty on success.
fn match_decoded(v cbor.Value, j json2.Any) string {
match j {
i64 {
match v {
cbor.IntNum {
if j >= 0 {
if v.negative || v.magnitude != u64(j) {
return 'int ${j} ↔ IntNum(neg=${v.negative}, mag=${v.magnitude})'
}
} else {
mag := u64(-(j + 1))
if !v.negative || v.magnitude != mag {
return 'int ${j} ↔ IntNum(neg=${v.negative}, mag=${v.magnitude})'
}
}
return ''
}
cbor.Tag {
// JSON ints beyond ±2^63 land here as i64-clamped or as a string;
// real bignum vectors use the `decoded` field with a u64 / negative,
// so this branch shouldn't fire for that case.
return 'unexpected Tag for plain int ${j}'
}
else {
return 'expected int ${j}, got ${v.type_name()}'
}
}
}
u64 {
if v is cbor.IntNum {
if v.negative || v.magnitude != j {
return 'uint ${j} ↔ IntNum(neg=${v.negative}, mag=${v.magnitude})'
}
return ''
}
return 'expected uint ${j}'
}
f64 {
// JSON has a single number type, so an integer-valued vector
// arrives here as f64 even when the CBOR is major type 0/1.
if v is cbor.FloatNum {
if math.is_nan(j) && math.is_nan(v.value) {
return ''
}
if v.value != j {
return 'float ${j} ↔ ${v.value}'
}
return ''
}
if v is cbor.IntNum {
// Beyond 2^53 JSON's f64 representation loses precision —
// we can't tell IntNum(2^64-1) from IntNum(2^64). Trust the
// roundtrip byte check and accept the structural shape.
f64_exact_int_max := f64(1) * f64(u64(1) << 53)
if math.abs(j) >= f64_exact_int_max {
return ''
}
expected_neg := j < 0
abs_val := if expected_neg { -j } else { j }
if abs_val != f64(u64(abs_val)) {
return 'float ${j} → IntNum: not integer-valued'
}
if expected_neg {
if !v.negative {
return 'expected negative IntNum for ${j}'
}
if u64(abs_val) - 1 != v.magnitude {
return 'IntNum mag ${v.magnitude} != ${u64(abs_val) - 1}'
}
} else {
if v.negative {
return 'expected non-negative IntNum for ${j}'
}
if u64(abs_val) != v.magnitude {
return 'IntNum mag ${v.magnitude} != ${u64(abs_val)}'
}
}
return ''
}
if v is cbor.Tag {
// Bignum (tag 2/3) representing a value beyond i64. Caller skips.
return 'tag-bignum (caller decides)'
}
return 'expected number ${j}'
}
bool {
if v is cbor.Bool && v.value == j {
return ''
}
return 'expected bool ${j}'
}
string {
if v is cbor.Text && v.value == j {
return ''
}
return 'expected text "${j}"'
}
json2.Null {
if v is cbor.Null {
return ''
}
return 'expected null'
}
[]json2.Any {
if v is cbor.Array {
if v.elements.len != j.len {
return 'array length ${v.elements.len} != ${j.len}'
}
for i, item in j {
sub := match_decoded(v.elements[i], item)
if sub != '' {
return 'array[${i}]: ${sub}'
}
}
return ''
}
return 'expected array'
}
map[string]json2.Any {
if v is cbor.Map {
if v.pairs.len != j.len {
return 'map size ${v.pairs.len} != ${j.len}'
}
for pair in v.pairs {
if pair.key !is cbor.Text {
// JSON can only express string keys; mixed-key maps live
// in the diagnostic-only set, so this is safe.
return 'non-text key in JSON-comparable map'
}
tk := pair.key as cbor.Text
if tk.value !in j {
return 'missing key ${tk.value}'
}
jv := j[tk.value] or { return 'missing key ${tk.value}' }
sub := match_decoded(pair.value, jv)
if sub != '' {
return 'map[${tk.value}]: ${sub}'
}
}
return ''
}
return 'expected map'
}
else {
return 'unsupported JSON kind ${typeof(j).name}'
}
}
}
// match_diagnostic enforces only structural sanity for entries that JSON
// can't directly express (NaN, Infinity, undefined, simple, tag, bignum).
fn match_diagnostic(v cbor.Value, diag string) string {
d := diag.trim_space()
match d {
'Infinity' {
if v is cbor.FloatNum && math.is_inf(v.value, 1) {
return ''
}
return 'expected +Inf'
}
'-Infinity' {
if v is cbor.FloatNum && math.is_inf(v.value, -1) {
return ''
}
return 'expected -Inf'
}
'NaN' {
if v is cbor.FloatNum && math.is_nan(v.value) {
return ''
}
return 'expected NaN'
}
'undefined' {
if v is cbor.Undefined {
return ''
}
return 'expected Undefined'
}
else {}
}
if d.starts_with('simple(') {
if v is cbor.Simple {
return ''
}
return 'expected Simple'
}
if d.starts_with("h'") {
if v is cbor.Bytes {
return ''
}
return 'expected Bytes'
}
if d.starts_with('(_') {
// Indefinite-length compound. Decoder collapses to definite Value.
if v is cbor.Bytes || v is cbor.Text || v is cbor.Array || v is cbor.Map {
return ''
}
return 'expected indef-collapsed compound'
}
// Tag forms: "0(\"...\")", "1(1363896240)", "23(h'…')", "24(h'…')", "32(\"…\")".
if d.contains('(') && d[0].is_digit() {
if v is cbor.Tag {
return ''
}
return 'expected Tag'
}
// Map literal "{1: 2, 3: 4}" — int-keyed map, can't be expressed in JSON.
if d.starts_with('{') {
if v is cbor.Map {
return ''
}
return 'expected Map'
}
return 'unrecognised diagnostic ${d}'
}
const fixture_path = os.join_path(os.dir(@FILE), 'appendix_a.json')
fn test_upstream_appendix_a_corpus() {
raw := os.read_file(fixture_path) or { panic('cannot read fixture: ${err}') }
parsed := json2.decode[json2.Any](raw) or { panic('json: ${err}') }
entries := parsed.as_array()
assert entries.len > 0, 'fixture is empty'
mut total := 0
mut roundtrip := 0
mut value_checks := 0
mut diag_checks := 0
mut failures := []string{}
for entry in entries {
obj := entry.as_map()
total++
hex_str := obj['hex'] or { json2.Any('') }.str()
// `f818` (simple(24) two-byte form) is well-formed under RFC 7049
// but RFC 8949 §3.3 explicitly forbids it. The upstream corpus
// predates RFC 8949 — our decoder correctly rejects it.
if hex_str == 'f818' {
cbor.decode[cbor.Value](h(hex_str), cbor.DecodeOpts{}) or {
assert err.msg().contains('1-byte form'), 'unexpected error for f818: ${err}'
continue
}
assert false, 'f818 should be rejected per RFC 8949 §3.3'
}
input := h(hex_str)
rt := if rt_any := obj['roundtrip'] {
rt_any.bool()
} else {
false
}
decoded_v := cbor.decode[cbor.Value](input, cbor.DecodeOpts{}) or {
failures << 'decode ${hex_str}: ${err}'
continue
}
// Compare structure where possible.
if decoded := obj['decoded'] {
diff := match_decoded(decoded_v, decoded)
if diff != '' {
// Bignums (decoded JSON ints beyond ±2^64) are represented as
// `Tag(2|3, Bytes)` on the wire. JSON loses them as i64-clamped
// values, so accept Tag/IntNum mismatch when the JSON is at the
// extreme range.
if decoded_v is cbor.Tag {
t := decoded_v as cbor.Tag
if t.number == 2 || t.number == 3 {
value_checks++
continue
}
}
failures << '${hex_str} decoded: ${diff}'
continue
}
value_checks++
} else if diag := obj['diagnostic'] {
diff := match_diagnostic(decoded_v, diag.str())
if diff != '' {
failures << '${hex_str} diagnostic "${diag}": ${diff}'
continue
}
diag_checks++
}
// For roundtrip=true entries, re-encode and compare bytes.
if rt {
out := cbor.encode_value(decoded_v, cbor.EncodeOpts{})!
if !bytes_eq(out, input) {
failures << '${hex_str} roundtrip: got ${hex.encode(out)}'
continue
}
roundtrip++
}
}
if failures.len > 0 {
for f in failures {
eprintln('FAIL: ${f}')
}
assert false, '${failures.len}/${total} upstream Appendix A vectors failed'
}
// Sanity: the upstream corpus has 80+ entries; if we ever see less,
// the fixture file is wrong.
assert total >= 80, 'corpus too small: ${total}'
assert roundtrip >= 60, 'too few roundtrip checks: ${roundtrip}'
assert value_checks > 0
assert diag_checks > 0
}