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/provider/release_test.go

188 lines
5.7 KiB

package provider_test
import (
"bytes"
"encoding/hex"
"errors"
"os"
"testing"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
// Spec v0.15, §47.1: every vector of release.json, replayed from the file
// alone, gets its result and its text: decoding, then step 10 against the
// pinned profile and the round of the vector.
func TestReleaseVectors(t *testing.T) {
var f testkit.ReleaseVectorFile
if err := testkit.ReadJSON("../testdata/vectors/release.json", &f); err != nil {
t.Fatal(err)
}
p := profile.Quicknet()
check := func(t *testing.T, in []byte, v testkit.ReleaseVector) {
r, err := provider.ParseRelease(in)
if err == nil {
err = provider.Verify(p, provider.Condition{Round: v.Round}, r)
}
if got := testkit.Result(err); got != v.Result {
t.Fatalf("%s, want %s (%v)", got, v.Result, err)
}
if err != nil && err.Error() != v.Text {
t.Fatalf("text %q, want %q", err.Error(), v.Text)
}
}
for _, v := range f.Objects {
t.Run(v.Name, func(t *testing.T) {
b, err := hex.DecodeString(v.Encoding)
if err != nil {
t.Fatal(err)
}
check(t, b, v)
if v.Result == testkit.ResultOK {
// A valid object is the deterministic encoding of its value.
r, _ := provider.DecodeRelease(b)
if again, err := provider.EncodeRelease(r); err != nil || !bytes.Equal(again, b) {
t.Fatalf("encoding again: %x, %v", again, err)
}
}
})
}
for _, v := range f.JSON {
t.Run("JSON "+v.Name, func(t *testing.T) { check(t, []byte(v.Input), v) })
}
if len(f.Objects) < 30 || len(f.JSON) < 10 {
t.Fatalf("%d objects and %d JSON inputs", len(f.Objects), len(f.JSON))
}
}
// The local archive of testdata supplies the release object of each round it
// holds, and nothing for a round it lacks or outside it, for another chain,
// or when its length is not the one its header announces.
func TestArchive(t *testing.T) {
b, err := os.ReadFile("../testdata/releases/" + testkit.ArchiveFile)
if err != nil {
t.Fatal(err)
}
if !provider.IsArchive(b) || provider.IsArchive(mustRead(t, "../testdata/releases/1000.cbor")) {
t.Fatal("IsArchive")
}
p := profile.Quicknet()
a := provider.NewArchive(bytes.NewReader(b), int64(len(b)))
for _, round := range []uint64{1000, 1001, 1004} {
got, err := a.Supply(p, provider.Condition{Round: round})
if err != nil || !bytes.Equal(got, mustRead(t, "../testdata/releases/"+testkit.ReleaseFileName(round))) {
t.Fatalf("round %d: %x, %v", round, got, err)
}
}
for _, round := range []uint64{999, 1002, 1003, 1005, 2000} {
if _, err := a.Supply(p, provider.Condition{Round: round}); !onlyUnavailable(err) {
t.Fatalf("round %d: %v", round, err)
}
}
other := p.Clone()
other.ChainHash[0] ^= 1
short := provider.NewArchive(bytes.NewReader(b[:len(b)-1]), int64(len(b)-1))
notArchive := mustRead(t, "../testdata/releases/1000.cbor")
for name, c := range map[string]struct {
a *provider.Archive
p *profile.Profile
}{
"another chain": {a, other},
"a byte missing": {short, p},
"a release object": {provider.NewArchive(bytes.NewReader(notArchive), int64(len(notArchive))), p},
"no byte": {provider.NewArchive(bytes.NewReader(nil), 0), p},
} {
if _, err := c.a.Supply(c.p, provider.Condition{Round: 1000}); !onlyUnavailable(err) {
t.Fatalf("%s: %v", name, err)
}
}
}
// onlyUnavailable reports whether err wraps ErrReleaseUnavailable and no
// other normative code.
func onlyUnavailable(err error) bool {
for _, e := range datekeys.All() {
if errors.Is(err, e) != (e == datekeys.ErrReleaseUnavailable) {
return false
}
}
return true
}
func mustRead(t *testing.T, path string) []byte {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return b
}
// EncodeRelease never writes an object that DecodeRelease rejects, and a
// release of the profile carries its chain hash.
func TestEncodeRelease(t *testing.T) {
p := profile.Quicknet()
r := testkit.Release(1000)
for _, bad := range []provider.Release{
r,
{Round: 0, Signature: r.Signature, ChainHash: p.ChainHash[:]},
{Round: 1 << 53, Signature: r.Signature, ChainHash: p.ChainHash[:]},
{Round: 1000, ChainHash: p.ChainHash[:]},
{Round: 1000, Signature: make([]byte, 97), ChainHash: p.ChainHash[:]},
} {
if _, err := provider.EncodeRelease(bad); !errors.Is(err, datekeys.ErrNonCanonicalCBOR) {
t.Fatalf("%+v: %v", bad, err)
}
}
b, err := provider.NewReleaseObject(p, r)
if err != nil || len(b) != 111 {
t.Fatalf("%d bytes, %v", len(b), err)
}
got, err := provider.DecodeRelease(b)
if err != nil || got.Round != 1000 || !bytes.Equal(got.Signature, r.Signature) || !bytes.Equal(got.ChainHash, p.ChainHash[:]) {
t.Fatalf("%+v, %v", got, err)
}
if _, err := provider.EncodeArchiveHeader(p.ChainHash[:], 0, 1); err == nil {
t.Fatal("archive from round 0")
}
}
// FuzzDecodeRelease: whatever the input, DecodeRelease returns exactly one
// normative code, and what it accepts encodes again to the same bytes.
func FuzzDecodeRelease(f *testing.F) {
f.Add(mustReadF(f, "../testdata/releases/1000.cbor"))
f.Add([]byte(`{"round":1000,"signature":"00"}`))
f.Fuzz(func(t *testing.T, b []byte) {
r, err := provider.ParseRelease(b)
if err != nil {
n := 0
for _, e := range datekeys.All() {
if errors.Is(err, e) {
n++
}
}
if n != 1 {
t.Fatalf("%d codes: %v", n, err)
}
return
}
if r.ChainHash == nil {
return
}
if again, err := provider.EncodeRelease(r); err != nil || !bytes.Equal(again, b) {
t.Fatalf("encoding again: %x, %v", again, err)
}
})
}
func mustReadF(f *testing.F, path string) []byte {
b, err := os.ReadFile(path)
if err != nil {
f.Fatal(err)
}
return b
}

Powered by TurnKey Linux.