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-dart/tool/mutation_go_texts.go

275 lines
8.9 KiB

Stage 4c: the vectors of Go for the head, the note and the opening tool/mutation_go_texts.go ports scripts/mutation-go-texts.go of datekeys-ts over the synced testdata of this repository, the tag spec-v0.11: it replays every case of the mutation corpus as the testkit of the reference does and writes the text of capsule.Open and its checks, with the detail of each step, in mutation_texts.json. Where Go and the corpus disagree on a code or a step it would say so in the case; Go at c531e93 and at the tag spec-v0.11 agree with all 210 cases, and give the same file, byte for byte. tool/open_go_vectors.go runs in an export of datekeys-go, since it uses internal/testkit, internal/cbortest and internal/inspectview, and writes: - open_cases.json: capsule.Open on every fixture with each of its credentials, and 117 openings of edited fixtures or with other options at each step that the corpus does not reach: the frame, the fields, the extensions and the bindings of a .dkk at step 9.a, the clock and the failures of the release source, the age headers of steps 11 and 17, a malformed X25519 stanza in INNER_ACCESS_AGE, CONTROL_CBOR and the BODY of format 3 sealed again, the sinks and the output that fail, the refusal of Accept and the unusable extensions of each object, with the text, the step, the checks, the release requests, the state of the sink and the content or the files; - open_heads.json: capsule.DecodeHead and EncodeHead of heads of a fixed seed, valid and broken in each layer of spec §69.1; - open_notes.json: extension.CheckNote, Note, Header.UnusableNote and extension.Standard with a note; - open_inspect.json: the text of capsule.Inspect for each of the 5110 mutations of inspect_differential.json, where Go and the file also agree, and the exact output of datekeys inspect -json with public notes; - open_vectors.g.dart: seven small fixtures, their records and a part of each file, for the tests that run compiled to JavaScript. The edited capsules are sealed again with the file keys and the nonces of the fixtures, as the testkit does, so the output is the same on every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
//go:build ignore
// Writes test/vectors/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, and the
// checks that it records, step by step with their details, so that the
// tests of datekeys-dart require the same text of the opening, byte for
// byte, and not only the same code and step. A port of
// scripts/mutation-go-texts.go of datekeys-ts, over the synced testdata of
// this repository, that of the branch v0.12 of datekeys-go at c531e93.
Stage 4c: the vectors of Go for the head, the note and the opening tool/mutation_go_texts.go ports scripts/mutation-go-texts.go of datekeys-ts over the synced testdata of this repository, the tag spec-v0.11: it replays every case of the mutation corpus as the testkit of the reference does and writes the text of capsule.Open and its checks, with the detail of each step, in mutation_texts.json. Where Go and the corpus disagree on a code or a step it would say so in the case; Go at c531e93 and at the tag spec-v0.11 agree with all 210 cases, and give the same file, byte for byte. tool/open_go_vectors.go runs in an export of datekeys-go, since it uses internal/testkit, internal/cbortest and internal/inspectview, and writes: - open_cases.json: capsule.Open on every fixture with each of its credentials, and 117 openings of edited fixtures or with other options at each step that the corpus does not reach: the frame, the fields, the extensions and the bindings of a .dkk at step 9.a, the clock and the failures of the release source, the age headers of steps 11 and 17, a malformed X25519 stanza in INNER_ACCESS_AGE, CONTROL_CBOR and the BODY of format 3 sealed again, the sinks and the output that fail, the refusal of Accept and the unusable extensions of each object, with the text, the step, the checks, the release requests, the state of the sink and the content or the files; - open_heads.json: capsule.DecodeHead and EncodeHead of heads of a fixed seed, valid and broken in each layer of spec §69.1; - open_notes.json: extension.CheckNote, Note, Header.UnusableNote and extension.Standard with a note; - open_inspect.json: the text of capsule.Inspect for each of the 5110 mutations of inspect_differential.json, where Go and the file also agree, and the exact output of datekeys inspect -json with public notes; - open_vectors.g.dart: seven small fixtures, their records and a part of each file, for the tests that run compiled to JavaScript. The edited capsules are sealed again with the file keys and the nonces of the fixtures, as the testkit does, so the output is the same on every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
//
// Each case is replayed as internal/testkit replays the corpus
// (MutationInput.Open), which a program 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 and the step of every case are checked against the corpus: a
// case where Go and the corpus disagree is written with go_error and
// go_step, and reported on the standard error, never resolved silently.
//
// Run it in the module of the reference implementation, which it imports,
// without changing anything there, from the datekeys-go next to this
// repository, on the branch v0.12 at c531e93:
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/mutation_go_texts.go \
// ../datekeys-dart/testdata > ../datekeys-dart/test/vectors/mutation_texts.json
package main
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"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"`
}
// caseOut is one case: its name, the text of the error or "ok", the checks
// as [step, name, ok, detail, code], and, when Go disagrees with the corpus,
// what Go gives.
type caseOut struct {
Name string `json:"name"`
Text string `json:"text"`
Checks [][]any `json:"checks"`
GoError string `json:"go_error,omitempty"`
GoStep int `json:"go_step,omitempty"`
}
type output struct {
Spec string `json:"spec"`
Generator string `json:"generator"`
Description string `json:"description"`
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 testdata/README.md, "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{
Spec: c.Spec,
Generator: "tool/mutation_go_texts.go",
Description: "The text of the error of capsule.Open for every case of testdata/vectors/mutations.json, or ok for a capsule that opens, and its checks as [step, name, ok, detail, code]; go_error and go_step where Go and the corpus disagree. See the header of tool/mutation_go_texts.go.",
}
disagree := 0
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)))
}
opened, err := capsule.Open(context.Background(), io.Discard, bytes.NewReader(dkc), o)
text, code, step := "ok", "ok", 0
if err != nil {
text, code = err.Error(), datekeys.Code(err)
}
co := caseOut{Name: m.Name, Text: text, Checks: [][]any{}}
for _, ch := range opened.Inspection.Checks {
co.Checks = append(co.Checks, []any{ch.Step, ch.Name, ch.OK, ch.Detail, ch.Error})
}
if err != nil {
step = opened.Inspection.Checks[len(opened.Inspection.Checks)-1].Step
}
if code != m.Error || step != m.Step {
disagree++
co.GoError, co.GoStep = code, step
fmt.Fprintf(os.Stderr, "%s: Go gives %s at step %d, the corpus says %s at step %d\n", m.Name, code, step, m.Error, m.Step)
}
out.Cases = append(out.Cases, co)
}
fmt.Fprintf(os.Stderr, "%d cases, %d where Go and the corpus disagree\n", len(out.Cases), disagree)
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetEscapeHTML(false)
if err := e.Encode(out.Cases); err != nil {
panic(err)
}
// One case per line.
var sb strings.Builder
head := func(v any) string {
var h bytes.Buffer
enc := json.NewEncoder(&h)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
panic(err)
}
return strings.TrimSuffix(h.String(), "\n")
}
sb.WriteString("{\n")
sb.WriteString(` "spec": ` + head(out.Spec) + ",\n")
sb.WriteString(` "generator": ` + head(out.Generator) + ",\n")
sb.WriteString(` "description": ` + head(out.Description) + ",\n")
sb.WriteString(` "cases": [`)
for i, co := range out.Cases {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + head(co))
}
sb.WriteString("\n ]\n}\n")
os.Stdout.WriteString(sb.String())
}

Powered by TurnKey Linux.