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.
128 lines
5.4 KiB
128 lines
5.4 KiB
package capsule_test
|
|
|
|
import (
|
|
"errors"
|
|
"maps"
|
|
"slices"
|
|
"testing"
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/capsule"
|
|
"g.activething.com/go/DateKeys/datekey"
|
|
"g.activething.com/go/DateKeys/internal/cbortest"
|
|
"g.activething.com/go/DateKeys/profile"
|
|
)
|
|
|
|
// swapped encodes m with the entries of keys a and b in each other's place.
|
|
func swapped(t *testing.T, m map[uint64]any, a, b uint64) []byte {
|
|
t.Helper()
|
|
var p cbortest.Pairs
|
|
for _, k := range slices.Sorted(maps.Keys(m)) {
|
|
switch k {
|
|
case a:
|
|
k = b
|
|
case b:
|
|
k = a
|
|
}
|
|
p = append(p, k, m[k])
|
|
}
|
|
out, err := cbortest.Marshal(p)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func with(m map[uint64]any, edit func(m map[uint64]any)) map[uint64]any {
|
|
c := maps.Clone(m)
|
|
edit(c)
|
|
return c
|
|
}
|
|
|
|
// Spec §58: the maps of PUBLIC_HEADER and CONTROL_CBOR are closed, their keys
|
|
// strictly ascending unsigned integers, and every required key is present.
|
|
func TestDecodeMapStructure(t *testing.T) {
|
|
dk := datekey.DateKey{ProfileID: profile.QuicknetID, Round: 1000}.Compact()
|
|
h := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(1), 2: make([]byte, 16), 3: dk, 4: uint64(0)}
|
|
c := map[uint64]any{0: capsule.ControlTypeTag, 1: uint64(1), 2: make([]byte, 32), 3: make([]byte, 32)}
|
|
exts := []any{ext("a", 1)}
|
|
for name, in := range map[string][]byte{
|
|
"missing capsule_id": marshal(t, with(h, func(m map[uint64]any) { delete(m, 2) })),
|
|
"missing access_policy": marshal(t, with(h, func(m map[uint64]any) { delete(m, 4) })),
|
|
"capsule_id as text": marshal(t, with(h, func(m map[uint64]any) { m[2] = "x" })),
|
|
"access_policy 2^53": marshal(t, with(h, func(m map[uint64]any) { m[4] = uint64(1) << 53 })),
|
|
"unknown key 7": marshal(t, with(h, func(m map[uint64]any) { m[7] = uint64(0) })),
|
|
"eight entries": marshal(t, with(h, func(m map[uint64]any) { m[5], m[6], m[7] = exts, []any{ext("b", 1)}, uint64(0) })),
|
|
"keys 3 and 4 swapped": swapped(t, h, 3, 4),
|
|
"text key": mustMarshal(t, cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), uint64(1), "2", make([]byte, 16)}),
|
|
} {
|
|
if _, err := capsule.DecodeHeader(in); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
|
|
t.Errorf("PUBLIC_HEADER %s: %v", name, err)
|
|
}
|
|
}
|
|
// access_policy is 0 or 1 (spec §25): a value whose low byte is 0 or 1
|
|
// is not a V1 policy.
|
|
for _, p := range []uint64{2, 255, 256, 257, 512, 65536, 65537, 1 << 32, 1<<32 + 1, 1<<53 - 256, 1<<53 - 255, 1<<53 - 1} {
|
|
if _, err := capsule.DecodeHeader(marshal(t, with(h, func(m map[uint64]any) { m[4] = p }))); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
|
|
t.Errorf("PUBLIC_HEADER access_policy %d: %v", p, err)
|
|
}
|
|
}
|
|
for name, in := range map[string][]byte{
|
|
"missing payload_identity": marshal(t, with(c, func(m map[uint64]any) { delete(m, 3) })),
|
|
"seven entries": marshal(t, with(c, func(m map[uint64]any) { m[4], m[5], m[6] = exts, []any{ext("b", 1)}, uint64(0) })),
|
|
"keys 2 and 3 swapped": swapped(t, c, 2, 3),
|
|
"text key": mustMarshal(t, cbortest.Pairs{uint64(0), capsule.ControlTypeTag, uint64(1), uint64(1), "2", make([]byte, 32)}),
|
|
} {
|
|
if _, err := capsule.DecodeControl(in, capsule.Format1); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
|
|
t.Errorf("CONTROL_CBOR %s: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustMarshal(t *testing.T, v any) []byte {
|
|
t.Helper()
|
|
b, err := cbortest.Marshal(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Spec §70: the schema version is reported as such whatever follows it, and
|
|
// a version that is missing or outside the profile is not a version.
|
|
func TestDecodeSchemaVersion(t *testing.T) {
|
|
for name, tc := range map[string]struct {
|
|
in cbortest.Pairs
|
|
want error
|
|
}{
|
|
"version 2, rest unreadable": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), uint64(2), uint64(2), cbortest.Raw{0xff}}, datekeys.ErrUnsupportedVersion},
|
|
"version missing": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(2), make([]byte, 16)}, datekeys.ErrNonCanonicalCBOR},
|
|
"version null": {cbortest.Pairs{uint64(0), capsule.HeaderTypeTag, uint64(1), nil}, datekeys.ErrNonCanonicalCBOR},
|
|
"version before type": {cbortest.Pairs{uint64(1), uint64(1), uint64(0), capsule.HeaderTypeTag}, datekeys.ErrNonCanonicalCBOR},
|
|
} {
|
|
if _, err := capsule.DecodeHeader(mustMarshal(t, tc.in)); !errors.Is(err, tc.want) {
|
|
t.Errorf("%s: got %v, want %v", name, err, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A PUBLIC_HEADER that breaks the CDDL and holds an invalid DateKey reports
|
|
// the CDDL violation: the DateKey is parsed after the map (spec §57, §63
|
|
// step 4).
|
|
func TestDecodeHeaderReportsTheCDDLFirst(t *testing.T) {
|
|
h := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(1), 2: make([]byte, 16), 3: "dk1_x", 4: uint64(0)}
|
|
if _, err := capsule.DecodeHeader(marshal(t, h)); !errors.Is(err, datekeys.ErrDateKeyInvalid) {
|
|
t.Fatalf("invalid DateKey alone: %v", err)
|
|
}
|
|
for name, edit := range map[string]func(m map[uint64]any){
|
|
"undefined access_policy": func(m map[uint64]any) { m[4] = uint64(2) },
|
|
"access_policy 256": func(m map[uint64]any) { m[4] = uint64(256) },
|
|
"extensions out of order": func(m map[uint64]any) { m[6] = []any{ext("b", 1), ext("a", 1)} },
|
|
"id in both arrays": func(m map[uint64]any) { m[5], m[6] = []any{ext("a", 1)}, []any{ext("a", 1)} },
|
|
} {
|
|
if _, err := capsule.DecodeHeader(marshal(t, with(h, edit))); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
|
|
t.Errorf("%s and an invalid DateKey: %v", name, err)
|
|
}
|
|
}
|
|
}
|