Initial project import
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user