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.
188 lines
5.7 KiB
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
|
|
}
|