You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/codec/codec_test.go

175 lines
5.3 KiB

package codec_test
import (
"encoding/hex"
"errors"
"math/rand/v2"
"strings"
"testing"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/codec"
)
type sample struct {
Type string `cbor:"0,keyasint"`
N uint64 `cbor:"1,keyasint"`
Bytes []byte `cbor:"2,keyasint"`
List []uint64 `cbor:"10,keyasint,omitempty"`
}
func mustHex(t *testing.T, s string) []byte {
t.Helper()
b, err := hex.DecodeString(s)
if err != nil {
t.Fatal(err)
}
return b
}
func TestMarshalIsCoreDeterministic(t *testing.T) {
b, err := codec.Marshal(sample{Type: "x", N: 23, Bytes: []byte{1}, List: []uint64{1, 500}})
if err != nil {
t.Fatal(err)
}
// {0: "x", 1: 23, 2: h'01', 10: [1, 500]} with keys sorted and shortest integers.
if got, want := hex.EncodeToString(b), "a400617801170241010a82011901f4"; got != want {
t.Fatalf("got %s, want %s", got, want)
}
var s sample
if err := codec.Unmarshal(b, &s); err != nil {
t.Fatal(err)
}
}
func TestUnmarshalRejectsNonCanonical(t *testing.T) {
for _, tc := range []struct{ name, hex string }{
{"integer not in shortest form", "a300617801181702410" + "1"},
{"keys out of order", "a301170061780241" + "01"},
{"duplicate key", "a4006178006179011702" + "4101"},
{"indefinite-length map", "bf00617801170241" + "01ff"},
{"indefinite-length byte string", "a3006178011702" + "5f4101ff"},
{"tag", "a3006178011702" + "c24101"},
{"unknown key", "a4006178011702410103" + "00"},
{"missing key", "a2006178011" + "7"},
{"trailing byte", "a30061780117024101" + "00"},
{"invalid UTF-8", "a30061ff0117024101"},
{"empty optional array present", "a400617801170241010a" + "80"},
{"wrong type", "a300617801617a024101"},
{"not a map", "83006178" + "01"},
{"empty input", ""},
} {
t.Run(tc.name, func(t *testing.T) {
var s sample
err := codec.Unmarshal(mustHex(t, tc.hex), &s)
if !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("got %v, want ErrNonCanonicalCBOR", err)
}
})
}
}
func TestValid(t *testing.T) {
for _, h := range []string{
"00", "17", "1818", "20", "3bffffffffffffffff", "40", "60", "80", "a0", "f4", "f5", "f6",
"f97e00", "f93c00", "fa47c35000", "a2016161026162", "a1416101", "8201820203",
} {
if err := codec.Valid(mustHex(t, h)); err != nil {
t.Errorf("%s rejected: %v", h, err)
}
}
for _, h := range []string{
"1817", // 23 encoded in two bytes
"f7", // undefined
"fb3ff0000000000000", // 1.0 as float64 instead of float16
"fa7fc00000", // NaN not in the canonical f97e00 form
"a2026162016161", // keys out of order
"c101", // tag
"9f01ff", // indefinite-length array
"a1810101", // array as map key
"0000", // two items
strings.Repeat("81", codec.MaxNestedLevels+4) + "00", // nesting beyond the limit
} {
if err := codec.Valid(mustHex(t, h)); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Errorf("%s accepted or wrong error: %v", h, err)
}
}
}
func TestCheckSchema(t *testing.T) {
b, _ := codec.Marshal(sample{Type: "datekeycap", N: 1, Bytes: []byte{}})
if err := codec.CheckSchema(b, "datekeycap", 1); err != nil {
t.Fatal(err)
}
if err := codec.CheckSchema(b, "datekeys-control", 1); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("type confusion: %v", err)
}
if err := codec.CheckSchema(b, "datekeycap", 2); !errors.Is(err, datekeys.ErrUnsupportedVersion) {
t.Fatalf("version: %v", err)
}
// A future version with unknown keys still reports the version.
future, _ := codec.Marshal(map[uint64]any{0: "datekeycap", 1: uint64(2), 99: "new"})
if err := codec.CheckSchema(future, "datekeycap", 1); !errors.Is(err, datekeys.ErrUnsupportedVersion) {
t.Fatalf("future version: %v", err)
}
}
func TestRoundTripProperty(t *testing.T) {
r := rand.New(rand.NewPCG(1, 2))
for range 2000 {
s := sample{Type: string(rune('a' + r.IntN(26))), N: r.Uint64() >> r.IntN(64), Bytes: make([]byte, r.IntN(40))}
for range r.IntN(4) {
s.List = append(s.List, r.Uint64()>>r.IntN(64))
}
b, err := codec.Marshal(s)
if err != nil {
t.Fatal(err)
}
var got sample
if err := codec.Unmarshal(b, &got); err != nil {
t.Fatalf("%x: %v", b, err)
}
b2, _ := codec.Marshal(got)
if string(b) != string(b2) {
t.Fatal("encoding is not stable")
}
}
}
func TestErrorsCarryTheNormativeCode(t *testing.T) {
if _, err := codec.Marshal(make(chan int)); err == nil {
t.Fatal("encoded a channel")
}
for _, in := range [][]byte{nil, {0xff}, {0x83, 0x01}, mustHex(t, "a10061")} {
if err := codec.CheckSchema(in, "datekeycap", 1); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("%x: %v", in, err)
}
var v struct {
A uint64 `cbor:"0,keyasint"`
}
if err := codec.Peek(in, &v); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("peek %x: %v", in, err)
}
}
}
func FuzzValid(f *testing.F) {
for _, h := range []string{"a400617801170241010a82011901f4", "f97e00", "a2016161026162", "9f01ff"} {
b, _ := hex.DecodeString(h)
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
if codec.Valid(b) != nil {
return
}
var v any
if err := codec.Unmarshal(b, &v); err != nil {
t.Fatalf("Valid accepted what Unmarshal rejects: %v", err)
}
re, err := codec.Marshal(v)
if err != nil || string(re) != string(b) {
t.Fatalf("accepted a non-canonical item %x", b)
}
})
}

Powered by TurnKey Linux.