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.
226 lines
7.0 KiB
226 lines
7.0 KiB
//go:build ignore
|
|
|
|
// Prints src/lib/dkc/testing/mutation-texts.json: the text of the error that
|
|
// capsule.Open of the Go reference gives for every case of
|
|
// testdata/vectors/mutations.json, or "ok" for a capsule that opens, so that
|
|
// vectors.test.ts requires the same text of open, byte for byte, and not
|
|
// only the same code and step (plan of format 3 in datekeys-ts, step 3).
|
|
//
|
|
// Each case is replayed as internal/testkit replays the corpus
|
|
// (MutationInput.Open), which a module outside the reference cannot import:
|
|
// the Quicknet profile pinned, or no profile for the registry "empty"; a
|
|
// release source that answers every request with the release of the case,
|
|
// or with "testkit: no release" and ERR_RELEASE_UNAVAILABLE; the .dkk
|
|
// decoded before, as OpenOptions.AccessKey; the identities of the case; the
|
|
// extensions of the case, known at (id, version) and whose data is valid
|
|
// only when it is valid_data, with the texts of testkit.KnownExtensions; a
|
|
// sink that discards the files; and the clock of the case. The code of every
|
|
// error is checked against the corpus: a mismatch panics.
|
|
//
|
|
// Run it from a scratch module that requires the reference implementation
|
|
// (replace g.activething.com/go/DateKeys => ../datekeys-go, GOFLAGS=-mod=mod
|
|
// and the go directive of the reference, so that its toolchain is used),
|
|
// passing the testdata directory of this repository:
|
|
//
|
|
// go run mutation-go-texts.go ../datekeys-ts/testdata > mutation-texts.json
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"filippo.io/age"
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/accesskey"
|
|
"g.activething.com/go/DateKeys/capsule"
|
|
"g.activething.com/go/DateKeys/extension"
|
|
"g.activething.com/go/DateKeys/profile"
|
|
"g.activething.com/go/DateKeys/provider"
|
|
)
|
|
|
|
type corpus struct {
|
|
Spec string `json:"spec"`
|
|
Cases []struct {
|
|
Name string `json:"name"`
|
|
DKC struct {
|
|
Base string `json:"base"`
|
|
Edits [][]json.RawMessage `json:"edits"`
|
|
} `json:"dkc"`
|
|
DKK string `json:"dkk"`
|
|
Identities []string `json:"identities"`
|
|
Release *struct {
|
|
Round uint64 `json:"round"`
|
|
Signature string `json:"signature"`
|
|
} `json:"release"`
|
|
Now string `json:"now"`
|
|
Registry string `json:"registry"`
|
|
Extensions []struct {
|
|
ID string `json:"id"`
|
|
Version uint64 `json:"version"`
|
|
ValidData string `json:"valid_data"`
|
|
} `json:"extensions"`
|
|
Error string `json:"error"`
|
|
Step int `json:"step"`
|
|
} `json:"cases"`
|
|
}
|
|
|
|
type caseOut struct {
|
|
Name string `json:"name"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type output struct {
|
|
Description string `json:"description"`
|
|
Generator string `json:"generator"`
|
|
Spec string `json:"spec"`
|
|
Cases []caseOut `json:"cases"`
|
|
}
|
|
|
|
// known is testkit.KnownExtensions: an extension.Registry and
|
|
// extension.DataValidator with the same texts.
|
|
type known []struct {
|
|
id string
|
|
version uint64
|
|
valid []byte
|
|
}
|
|
|
|
func (k known) find(id string, version uint64) int {
|
|
for i := range k {
|
|
if k[i].id == id && k[i].version == version {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (k known) Known(id string, version uint64) bool { return k.find(id, version) >= 0 }
|
|
|
|
func (k known) ValidateData(e extension.Extension) error {
|
|
i := k.find(e.ID, e.Version)
|
|
if i < 0 {
|
|
return fmt.Errorf("extension %s version %d is not known", e.ID, e.Version)
|
|
}
|
|
if !bytes.Equal(k[i].valid, e.Data) {
|
|
return fmt.Errorf("data %x is not %x", e.Data, k[i].valid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// source is testkit's singleSource.
|
|
type source struct{ release *provider.Release }
|
|
|
|
func (s source) Fetch(context.Context, *profile.Profile, provider.Condition) (provider.Release, error) {
|
|
if s.release == nil {
|
|
return provider.Release{}, fmt.Errorf("testkit: no release: %w", datekeys.ErrReleaseUnavailable)
|
|
}
|
|
return *s.release, nil
|
|
}
|
|
|
|
// discard is testkit's DiscardSink.
|
|
type discard struct{}
|
|
|
|
func (discard) Begin(*capsule.Head) error { return nil }
|
|
func (discard) Create(int) (io.WriteCloser, error) { return nopCloser{io.Discard}, nil }
|
|
func (discard) Commit() error { return nil }
|
|
func (discard) Abort() {}
|
|
|
|
type nopCloser struct{ io.Writer }
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
func must[T any](v T, err error) T {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// applyEdits is the reading of README "Edited files": [at, delete, insert]
|
|
// on the offsets of the unmodified base, sorted and not overlapping.
|
|
func applyEdits(base []byte, edits [][]json.RawMessage) []byte {
|
|
var out []byte
|
|
pos := 0
|
|
for _, e := range edits {
|
|
var at, del int
|
|
var insert string
|
|
if len(e) != 3 || json.Unmarshal(e[0], &at) != nil || json.Unmarshal(e[1], &del) != nil || json.Unmarshal(e[2], &insert) != nil {
|
|
panic("an edit is [at, delete, insert]")
|
|
}
|
|
if at < pos || at+del > len(base) {
|
|
panic("edits out of order or beyond the base")
|
|
}
|
|
out = append(append(out, base[pos:at]...), must(hex.DecodeString(insert))...)
|
|
pos = at + del
|
|
}
|
|
return append(out, base[pos:]...)
|
|
}
|
|
|
|
func main() {
|
|
dir := os.Args[1]
|
|
var c corpus
|
|
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(dir, "vectors", "mutations.json"))), &c); err != nil {
|
|
panic(err)
|
|
}
|
|
out := output{
|
|
Description: "The text of the error of capsule.Open for every case of testdata/vectors/mutations.json, or ok for a capsule that opens; see the header of scripts/mutation-go-texts.go.",
|
|
Generator: "scripts/mutation-go-texts.go",
|
|
Spec: c.Spec,
|
|
}
|
|
for _, m := range c.Cases {
|
|
var base []byte
|
|
if m.DKC.Base != "" {
|
|
base = must(os.ReadFile(filepath.Join(dir, "fixtures", m.DKC.Base)))
|
|
}
|
|
dkc := applyEdits(base, m.DKC.Edits)
|
|
reg := must(profile.Default())
|
|
if m.Registry == "empty" {
|
|
reg = must(profile.NewRegistry())
|
|
}
|
|
var src source
|
|
if m.Release != nil {
|
|
src.release = &provider.Release{Round: m.Release.Round, Signature: must(hex.DecodeString(m.Release.Signature))}
|
|
}
|
|
now := must(time.Parse(time.RFC3339Nano, m.Now))
|
|
o := capsule.OpenOptions{Registry: reg, Source: src, Now: func() time.Time { return now }, Sink: discard{}}
|
|
if m.Extensions != nil {
|
|
var k known
|
|
for _, x := range m.Extensions {
|
|
k = append(k, struct {
|
|
id string
|
|
version uint64
|
|
valid []byte
|
|
}{x.ID, x.Version, must(hex.DecodeString(x.ValidData))})
|
|
}
|
|
o.Extensions = k
|
|
}
|
|
if m.DKK != "" {
|
|
o.AccessKey = must(accesskey.Decode(bytes.NewReader(must(hex.DecodeString(m.DKK)))))
|
|
}
|
|
for _, s := range m.Identities {
|
|
o.Identities = append(o.Identities, must(age.ParseX25519Identity(s)))
|
|
}
|
|
_, err := capsule.Open(context.Background(), io.Discard, bytes.NewReader(dkc), o)
|
|
text, code := "ok", "ok"
|
|
if err != nil {
|
|
text, code = err.Error(), datekeys.Code(err)
|
|
}
|
|
if code != m.Error {
|
|
panic(fmt.Sprintf("%s: %s, the corpus says %s", m.Name, text, m.Error))
|
|
}
|
|
out.Cases = append(out.Cases, caseOut{m.Name, text})
|
|
}
|
|
e := json.NewEncoder(os.Stdout)
|
|
e.SetEscapeHTML(false)
|
|
e.SetIndent("", " ")
|
|
if err := e.Encode(out); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|