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/open_go_vectors.go

1761 lines
65 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>
3 days ago
//go:build ignore
// Writes the vectors of the head, the public note, the inspection and the
// opening of datekeys-dart, stage 4c of docs/PLAN_dart.md: the results, the
// normative codes, the texts and the checks of the Go reference on
//
// - open_heads.json: HEAD_CBOR (capsule.DecodeHead), heads built with a
// fixed seed, valid and broken in each layer of spec §69.1, with no
// registry and with one; and capsule.EncodeHead of values;
// - open_notes.json: the public note (extension.CheckNote on texts and on
// bytes, extension.Note and Header.UnusableNote on extension arrays,
// extension.Standard through CheckNoncriticalIn and CheckWrite);
// - open_inspect.json: the text of the error of capsule.Inspect for each
// mutation of testdata/vectors/inspect_differential.json, and the exact
// output of `datekeys inspect -json` (internal/inspectview) for capsules
// with a public note, valid or not, and unusable extensions;
// - open_cases.json: capsule.Open on every fixture with each of its
// credentials, and on fixtures edited or opened with other options at
// each step of spec §63 that the mutation corpus does not reach: the
// framing of a .dkk at step 9.a, its fields, extensions and bindings, the
// clock and the failures of the release source at step 9, the headers
// of age at steps 11 and 17, a malformed X25519 stanza in
// INNER_ACCESS_AGE, CONTROL_CBOR sealed again, BODY of format 3 sealed
// again, the sinks and the output that fail, the refusal of Accept and
// the unusable extensions of each object.
//
// Every case of open_cases.json records the text of the error, its code and
// step, and the checks of capsule.Open with their details. The edited
// capsules are sealed again as the testkit of the reference does, with the
// file keys of the fixtures and their nonces, so the output is the same on
// every run. It also writes test/vectors/open_vectors.g.dart: the fixtures
// it names and a part of each file, as Dart constants for the tests that
// also run compiled to JavaScript.
//
// It imports internal packages of the reference (internal/testkit,
// internal/cbortest and internal/inspectview), so it runs in an export of
// datekeys-go made with git archive, without changing the repository, on
// the branch v0.12 at c531e93:
//
// commit=$(git -C ../datekeys-go rev-parse v0.12)
// root=$PWD
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>
3 days ago
// tmp=$(mktemp -d)
// git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
// cp tool/open_go_vectors.go "$tmp"
// (cd "$tmp" && go run ./open_go_vectors.go -source "$commit" \
// -testdata "$root/testdata" -out "$root/test/vectors")
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>
3 days ago
// rm -rf "$tmp"
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"hash"
"io"
"math/rand/v2"
"os"
"path/filepath"
"slices"
"strings"
"time"
"unicode/utf8"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/cbortest"
"g.activething.com/go/DateKeys/internal/inspectview"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
const specVersion = "0.11"
// The fixed seed of every random choice.
var rng = rand.New(rand.NewPCG(0x4c0a052026, 0x0de4c))
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
func hx(b []byte) string { return hex.EncodeToString(b) }
func pick[T any](xs ...T) T { return xs[rng.IntN(len(xs))] }
func randBytes(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(rng.IntN(256))
}
return b
}
// Case is one vector: a JSON object.
type Case map[string]any
// outcome records the result of err in c: "ok", the normative code, or
// "error" for an error without one, with its text.
func outcome(c Case, err error) Case {
if err == nil {
c["result"] = "ok"
return c
}
code := datekeys.Code(err)
if code == "" {
code = "error"
}
c["result"] = code
text := err.Error()
if !utf8.ValidString(text) {
panic("an error text that is not valid UTF-8: " + text)
}
c["text"] = text
return c
}
// ---------------------------------------------------------------------------
// Fixtures
var fixtureDir string
var loaded = map[string]*testkit.LoadedFixture{}
func fx(name string) *testkit.LoadedFixture {
if f, ok := loaded[name]; ok {
return f
}
f := must(testkit.LoadFixture(fixtureDir, name))
loaded[name] = f
return f
}
// fixtureNames are the 26 capsules of the synced testdata, sorted.
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>
3 days ago
func fixtureNames() []string {
var names []string
for _, e := range must(os.ReadDir(fixtureDir)) {
if n, ok := strings.CutSuffix(e.Name(), ".dkc"); ok {
names = append(names, n)
}
}
slices.Sort(names)
return names
}
// edits are the edits that turn the fixture base into out, as
// testdata/README.md writes them.
func edits(base string, out []byte) Case {
b := fx(strings.TrimSuffix(base, ".dkc")).DKC
es := [][]any{}
for _, e := range testkit.Splice(b, out) {
es = append(es, []any{e.At, e.Delete, hx(e.Insert)})
}
return Case{"base": base, "edits": es}
}
// ---------------------------------------------------------------------------
// Openings
// opening is what capsule.Open is given in one case.
type opening struct {
name string
base string // the fixture the capsule derives from
dkc []byte
release *provider.Release
sourceErr error // when set, the source fails with it
now time.Time
empty bool // no pinned profile
known testkit.KnownExtensions
rejectAll string // a registry that knows everything and rejects all data
identities []string
dkk []byte // a .dkk decoded before
material []byte // the material of that .dkk, replaced
dkkFile []byte // a .dkk still encoded
sinkFail string // begin, create i, write i, close i or commit
outputFail bool
refuse bool
}
// rejecting knows every extension and rejects the data of all.
type rejecting string
func (rejecting) Known(string, uint64) bool { return true }
func (r rejecting) ValidateData(extension.Extension) error { return errors.New(string(r)) }
func (r rejecting) RegisteredIn(string, uint64, extension.Object, extension.Array) bool {
return true
}
type source struct {
release *provider.Release
err error
calls int
}
func (s *source) Fetch(context.Context, *profile.Profile, provider.Condition) (provider.Release, error) {
s.calls++
if s.err != nil {
return provider.Release{}, s.err
}
if s.release == nil {
return provider.Release{}, fmt.Errorf("testkit: no release: %w", datekeys.ErrReleaseUnavailable)
}
return *s.release, nil
}
// recordingSink keeps the files and fails at the call named.
type recordingSink struct {
fail string
files [][]byte
committed bool
aborted bool
begun bool
}
func (s *recordingSink) Begin(h *capsule.Head) error {
if s.fail == "begin" {
return errors.New("no begin")
}
s.begun = true
s.files = make([][]byte, len(h.Files))
return nil
}
func (s *recordingSink) Create(i int) (io.WriteCloser, error) {
if s.fail == fmt.Sprintf("create %d", i) {
return nil, fmt.Errorf("no create %d", i)
}
return &recordingFile{s, i}, nil
}
func (s *recordingSink) Commit() error {
if s.fail == "commit" {
return errors.New("no commit")
}
s.committed = true
return nil
}
func (s *recordingSink) Abort() { s.aborted = true }
type recordingFile struct {
s *recordingSink
i int
}
func (f *recordingFile) Write(b []byte) (int, error) {
if f.s.fail == fmt.Sprintf("write %d", f.i) {
return 0, fmt.Errorf("no write %d", f.i)
}
f.s.files[f.i] = append(f.s.files[f.i], b...)
return len(b), nil
}
func (f *recordingFile) Close() error {
if f.s.fail == fmt.Sprintf("close %d", f.i) {
return fmt.Errorf("no close %d", f.i)
}
return nil
}
// output records the length and the SHA-256 of what it receives.
type output struct {
fail bool
h hash.Hash
n int
}
func (w *output) Write(b []byte) (int, error) {
if w.fail {
return 0, errors.New("disk full")
}
w.h.Write(b)
w.n += len(b)
return len(b), nil
}
func unusableJSON(us []extension.Unusable) [][]any {
out := [][]any{}
for _, u := range us {
out = append(out, []any{u.ID, u.Version, u.Err.Error()})
}
return out
}
func checksJSON(cs []capsule.CheckResult) [][]any {
out := [][]any{}
for _, c := range cs {
out = append(out, []any{c.Step, c.Name, c.OK, c.Detail, c.Error})
}
return out
}
func (o opening) run() Case {
reg := testkit.Registry()
if o.empty {
reg = must(profile.NewRegistry())
}
src := &source{release: o.release, err: o.sourceErr}
sink := &recordingSink{fail: o.sinkFail}
now := o.now
opts := capsule.OpenOptions{Registry: reg, Source: src, Now: func() time.Time { return now }, Sink: sink}
c := Case{"name": o.name, "dkc": edits(o.base, o.dkc), "now": now.Format(time.RFC3339Nano)}
if o.release != nil {
c["release"] = Case{"round": o.release.Round, "signature": hx(o.release.Signature)}
} else {
c["release"] = nil
}
if o.sourceErr != nil {
se := Case{"text": o.sourceErr.Error()}
if code := datekeys.Code(o.sourceErr); code != "" {
se["code"] = code
se["text"] = strings.TrimSuffix(o.sourceErr.Error(), ": "+code)
}
c["source_error"] = se
}
if o.empty {
c["registry"] = "empty"
}
switch {
case o.known != nil:
opts.Extensions = o.known
var ks []Case
for _, k := range o.known {
ks = append(ks, Case{"id": k.ID, "version": k.Version, "valid_data": hx(k.ValidData)})
}
c["extensions"] = ks
case o.rejectAll != "":
opts.Extensions = rejecting(o.rejectAll)
c["reject_all"] = o.rejectAll
}
if o.dkk != nil {
k := must(accesskey.Decode(bytes.NewReader(o.dkk)))
c["dkk"] = hx(o.dkk)
if o.material != nil {
k.Material = o.material
c["dkk_material"] = hx(o.material)
}
opts.AccessKey = k
}
if o.dkkFile != nil {
opts.AccessKeyFile = bytes.NewReader(o.dkkFile)
c["dkk_file"] = hx(o.dkkFile)
}
if o.identities != nil {
c["identities"] = o.identities
for _, s := range o.identities {
opts.Identities = append(opts.Identities, must(age.ParseX25519Identity(s)))
}
}
if o.sinkFail != "" {
c["sink_fail"] = o.sinkFail
}
if o.outputFail {
c["output_fail"] = true
}
if o.refuse {
c["accept"] = "refuse"
opts.Accept = func(capsule.Verdicts) error { return errors.New("not trusted") }
}
out := &output{fail: o.outputFail, h: sha256.New()}
opened, err := capsule.Open(context.Background(), out, bytes.NewReader(o.dkc), opts)
switch {
case err == nil:
c["result"], c["text"] = "ok", "ok"
case o.refuse && datekeys.Code(err) == "":
c["result"], c["text"] = "refused", err.Error()
default:
outcome(c, err)
}
checks := opened.Inspection.Checks
c["checks"] = checksJSON(checks)
if err != nil && checks[len(checks)-1].OK && c["result"] != "refused" {
panic(o.name + ": a failure without a failed check")
}
c["release_requests"] = src.calls
if err == nil {
if opened.Format == capsule.Format3 {
files := [][]any{}
for i, f := range opened.Head.Files {
sum := sha256.Sum256(sink.files[i])
files = append(files, []any{f.Path, f.Size, hx(sum[:])})
}
c["files"] = files
} else {
c["content"] = Case{"length": out.n, "sha256": hx(out.h.Sum(nil))}
}
}
switch {
case sink.committed:
c["sink"] = "committed"
case sink.aborted:
c["sink"] = "aborted"
case sink.begun:
c["sink"] = "begun"
default:
c["sink"] = "untouched"
}
unusable := Case{}
if opened.Inspection != nil && len(opened.Inspection.UnusableExtensions) > 0 {
unusable["header"] = unusableJSON(opened.Inspection.UnusableExtensions)
}
if len(opened.UnusableControlExtensions) > 0 {
unusable["control"] = unusableJSON(opened.UnusableControlExtensions)
}
if len(opened.UnusableAccessKeyExtensions) > 0 {
unusable["dkk"] = unusableJSON(opened.UnusableAccessKeyExtensions)
}
if len(opened.UnusableHeadExtensions) > 0 {
unusable["head"] = unusableJSON(opened.UnusableHeadExtensions)
}
if len(unusable) > 0 {
c["unusable"] = unusable
}
return c
}
// open is the opening of the fixture f as it is, with its release and its
// clock.
func open(name string, f *testkit.LoadedFixture) opening {
r := f.Published
return opening{name: name, base: f.File, dkc: f.DKC, release: &r, now: f.Unlock}
}
// ---------------------------------------------------------------------------
// The fixtures, with each credential
func fixtureCases() []Case {
var out []Case
for _, name := range fixtureNames() {
f := fx(name)
if f.AccessPolicy == capsule.TimeOnly.String() {
out = append(out, open(name, f).run())
continue
}
var all []opening
if f.DKK != nil {
o := open(name+": the .dkk, still encoded", f)
o.dkkFile = f.DKK
all = append(all, o)
o = open(name+": the .dkk, decoded", f)
o.dkk = f.DKK
all = append(all, o)
}
for i, id := range f.Identities {
o := open(fmt.Sprintf("%s: identity %d", name, i+1), f)
o.identities = []string{id}
all = append(all, o)
}
if len(f.Identities) > 0 {
o := open(name+": every credential", f)
o.identities = f.Identities
o.dkkFile = f.DKK
all = append(all, o)
}
for _, o := range all {
out = append(out, o.run())
}
}
return out
}
// ---------------------------------------------------------------------------
// The cases at each step
// dkkWith returns the .dkk of f with its body decoded as a map, changed by
// edit and encoded again, in a frame of its length.
func dkkWith(f *testkit.LoadedFixture, edit func(m map[uint64]any)) []byte {
m := must(cbortest.UnmarshalMap(f.DKK[12:]))
edit(m)
body := must(cbortest.Marshal(m))
pre := []byte{'D', 'K', 'K', '1', 1, 0, 0, 0, 0, 0, 0, 0}
binary.BigEndian.PutUint32(pre[8:], uint32(len(body)))
return append(pre, body...)
}
func set(b []byte, i int, v byte) []byte {
c := bytes.Clone(b)
c[i] = v
return c
}
// macEdit changes one character in the middle of the MAC of the age header
// at the start of file: the header stays canonical Base64, and its MAC no
// longer verifies.
func macEdit(file []byte) []byte {
i := bytes.Index(file, []byte("\n--- ")) + 10
return set(file, i, map[bool]byte{true: 'B', false: 'A'}[file[i] == 'A'])
}
func reframe(f *testkit.LoadedFixture, header, sealed, payload []byte) []byte {
return testkit.Reframe(f.Parts.Prelude, header, sealed, payload)
}
// withBody returns the opening of f, a time_only fixture of format 3, with
// BODY replaced and followed by the zeros of its padding up to P = rule(L);
// plain, when given, edits that plaintext.
func withBody(name string, f *testkit.LoadedFixture, body []byte, plain func(p []byte) []byte) opening {
in := must(f.WithBody(body, plain))
o := open(name, f)
o.dkc = in.DKC
return o
}
// withPlaintext returns the opening of f with the plaintext of its
// PAYLOAD_AGE replaced, L and P unchanged.
func withPlaintext(name string, f *testkit.LoadedFixture, plaintext []byte) opening {
in := must(f.WithPayloadPlaintext(plaintext))
o := open(name, f)
o.dkc = in.DKC
return o
}
// body3 is the BODY of a format 3 fixture, split.
type body3 struct {
frame capsule.BodyFrame
area []byte
head []byte
content []byte
}
func readBody3(f *testkit.LoadedFixture) body3 {
b := must(os.ReadFile(filepath.Join(fixtureDir, f.PlaintextFile)))
frame := must(capsule.ParseBodyFrame(b[:capsule.BodyFrameSize], uint64(len(b))))
a := capsule.BodyFrameSize + int(frame.AreaLen)
h := a + int(frame.HeadLen)
return body3{frame: frame, area: b[capsule.BodyFrameSize:a], head: b[a:h], content: b[h:]}
}
func (b body3) bytes() []byte {
fb := b.frame.Bytes()
return testkit.Join(fb[:], b.area, b.head, b.content)
}
// withHead returns BODY with its head replaced by the map of the head
// changed by edit.
func (b body3) withHead(edit func(h map[uint64]any)) []byte {
m := must(cbortest.UnmarshalMap(b.head))
edit(m)
b.head = must(cbortest.Marshal(m))
b.frame.HeadLen = uint32(len(b.head))
return b.bytes()
}
func plaintextOf(f *testkit.LoadedFixture) []byte {
return must(os.ReadFile(filepath.Join(fixtureDir, f.PlaintextFile)))
}
func zeros(n int) []byte { return make([]byte, n) }
func stepCases() []Case {
var os_ []opening
add := func(o opening) { os_ = append(os_, o) }
to, tox := fx("time_only"), fx("time_only_extensions")
tk, tkr := fx("time_and_key_portable"), fx("time_and_key_recipients")
tox2 := fx("format2_time_only_extensions")
tk2r := fx("format2_time_and_key_recipients")
s3, tree3, tk3 := fx("format3_single"), fx("format3_tree"), fx("format3_time_and_key_portable")
// Steps 3 and 7 through the opening, which stops there without a
// request.
{
o := open("PUBLIC_HEADER cut short", tox)
o.dkc = tox.DKC[:capsule.PreludeSize+10]
add(o)
h := must(testkit.RawHeader([16]byte(must(capsule.DecodeHeader(tox.Parts.Header)).CapsuleID), dkRound(83903165812), 0))
o = open("a DateKey round after 9999-12-31T23:59:59Z", tox)
o.dkc = reframe(tox, h, tox.Parts.Sealed, tox.Parts.Payload)
add(o)
h = must(testkit.RawHeader([16]byte(must(capsule.DecodeHeader(tox.Parts.Header)).CapsuleID), dkRound(83903165811), 0))
o = open("a DateKey of the last round, with the tlock stanza of round 2000", tox)
o.dkc = reframe(tox, h, tox.Parts.Sealed, tox.Parts.Payload)
o.now = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
add(o)
}
// Step 9.a: the .dkk as an object, decoded at step 9, in the order of
// spec §40, §41 and §69.1.
dkkCases := []struct {
name string
file []byte
}{
{"three bytes DKK", []byte("DKK")},
{"another magic", set(tk.DKK, 3, '2')},
{"a prelude of 11 bytes", tk.DKK[:11]},
{"framing version 2", set(tk.DKK, 4, 2)},
{"FLAGS 1", set(tk.DKK, 5, 1)},
{"RESERVED 1", set(tk.DKK, 7, 1)},
{"BODY_LEN 0", append(append(bytes.Clone(tk.DKK[:8]), 0, 0, 0, 0), tk.DKK[12:]...)},
{"BODY_LEN above 16 MiB", append(append(bytes.Clone(tk.DKK[:8]), 1, 0, 0, 1), tk.DKK[12:]...)},
{"a body cut short", tk.DKK[:len(tk.DKK)-1]},
{"a byte after the body", append(bytes.Clone(tk.DKK), 0)},
{"a body that is not canonical CBOR", set(tk.DKK, 12, 0xbf)},
{"another type tag", dkkWith(tk, func(m map[uint64]any) { m[0] = "datekeys-access-kex" })},
{"schema version 2", dkkWith(tk, func(m map[uint64]any) { m[1] = uint64(2) })},
{"an unknown key", dkkWith(tk, func(m map[uint64]any) { m[9] = uint64(1) })},
{"access_type ed25519", dkkWith(tk, func(m map[uint64]any) { m[4] = "ed25519" })},
{"access_material of 31 bytes", dkkWith(tk, func(m map[uint64]any) { m[5] = m[5].([]byte)[:31] })},
{"an unknown critical extension", dkkWith(tk, func(m map[uint64]any) {
m[7] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1)}}
})},
{"another capsule_id", dkkWith(tk, func(m map[uint64]any) { m[3] = bytes.Repeat([]byte{7}, 16) })},
{"another capsule_id and an unknown critical extension", dkkWith(tk, func(m map[uint64]any) {
m[3] = bytes.Repeat([]byte{7}, 16)
m[7] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1)}}
})},
{"the capsule_digest of another file", dkkWith(tk, func(m map[uint64]any) {
m[6] = map[uint64]any{0: bytes.Repeat([]byte{9}, 32)}
})},
{"no capsule_digest", dkkWith(tk, func(m map[uint64]any) { delete(m, 6) })},
{"an unknown noncritical extension", dkkWith(tk, func(m map[uint64]any) {
m[8] = []any{map[uint64]any{0: "org.example.note", 1: uint64(1), 2: []byte("hola")}}
})},
}
for _, d := range dkkCases {
o := open("the .dkk: "+d.name, tk)
o.dkkFile = d.file
add(o)
}
{
o := open("the .dkk: a known critical extension with invalid data", tk)
o.dkkFile = dkkWith(tk, func(m map[uint64]any) {
m[7] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1), 2: []byte("no")}}
})
o.known = testkit.KnownExtensions{{ID: "org.example.lock", Version: 1, ValidData: []byte("ok")}}
add(o)
o = open("the .dkk: a known noncritical extension with invalid data", tk)
o.dkkFile = dkkWith(tk, func(m map[uint64]any) {
m[8] = []any{map[uint64]any{0: "org.example.note", 1: uint64(1), 2: []byte("hola")}}
})
o.known = testkit.KnownExtensions{{ID: "org.example.note", Version: 1, ValidData: []byte("ok")}}
add(o)
o = open("the .dkk with an extension, every extension rejected", tk)
o.dkkFile = must(os.ReadFile(filepath.Join(fixtureDir, "time_and_key_portable_extension.dkk")))
o.rejectAll = "not today"
add(o)
o = open("the .dkk, decoded, with access_material of 31 bytes", tk)
o.dkk, o.material = tk.DKK, unhex(must(accessMaterial(tk.DKK)))[1:]
add(o)
o = open("time_only and a .dkk that is not one, which plays no part", to)
o.dkkFile = []byte("DKK")
add(o)
o = open("time_only and a decoded .dkk of another capsule, which plays no part", tox)
o.dkk = tk.DKK
add(o)
o = open("time_and_key without credentials, before the round time", tk)
o.now = tk.Unlock.Add(-time.Hour)
add(o)
o = open("time_and_key with FLAGS 1 and a .dkk without magic", tk)
o.dkc = set(tk.DKC, 5, 1)
o.dkkFile = []byte("XKK1")
add(o)
o = open("format 3: the .dkk of another capsule", tk3)
o.dkkFile = tk.DKK
add(o)
o = open("format 2: the .dkk and three identities", tk2r)
o.dkkFile = tk2r.DKK
o.identities = tk2r.Identities
add(o)
o = open("format 2: the .dkk and an identity that is not a recipient", tk2r)
o.dkkFile = tk2r.DKK
o.identities = []string{testkit.Stranger().String()}
add(o)
}
// Step 9.c: the clock, and the failures of the source.
{
o := open("one second before the round time", to)
o.now = to.Unlock.Add(-time.Second)
add(o)
o = open("one nanosecond before the round time", to)
o.now = to.Unlock.Add(-time.Nanosecond)
add(o)
o = open("one nanosecond after the round time", tox)
o.now = tox.Unlock.Add(time.Nanosecond)
add(o)
o = open("a year after the round time", tox)
o.now = tox.Unlock.AddDate(1, 0, 0)
add(o)
for _, e := range []error{
fmt.Errorf("relay: not yet: %w", datekeys.ErrReleaseUnavailable),
fmt.Errorf("relay: bad: %w", datekeys.ErrReleaseInvalid),
fmt.Errorf("relay: round: %w", datekeys.ErrRoundMismatch),
errors.New("connection refused"),
} {
o = open("the source fails: "+e.Error(), tox)
o.release, o.sourceErr = nil, e
add(o)
}
o = open("time_and_key: the source fails after the credentials", tk)
o.dkkFile = tk.DKK
o.release, o.sourceErr = nil, errors.New("connection refused")
add(o)
}
// Step 10: a release of another round, supplied directly.
{
r := testkit.Release(2000)
o := open("the valid release of round 2000 for round 1000", to)
o.release = &r
add(o)
}
// Step 11: the age header of OUTER_TIME_AGE and its STREAM.
{
sealed := tox.Parts.Sealed
o := open("OUTER_TIME_AGE with its MAC edited", tox)
o.dkc = reframe(tox, tox.Parts.Header, macEdit(sealed), tox.Parts.Payload)
add(o)
o = open("OUTER_TIME_AGE one byte short", tox)
o.dkc = reframe(tox, tox.Parts.Header, sealed[:len(sealed)-1], tox.Parts.Payload)
add(o)
o = open("OUTER_TIME_AGE with a byte more", tox)
o.dkc = reframe(tox, tox.Parts.Header, append(bytes.Clone(sealed), 0), tox.Parts.Payload)
add(o)
o = open("OUTER_TIME_AGE cut after its header", tox)
hdr := must(testkit.HeaderLen(sealed))
o.dkc = reframe(tox, tox.Parts.Header, sealed[:hdr+8], tox.Parts.Payload)
add(o)
o = open("OUTER_TIME_AGE without its STREAM", tox)
o.dkc = reframe(tox, tox.Parts.Header, sealed[:hdr+16], tox.Parts.Payload)
add(o)
}
// Steps 12 and 13: INNER_ACCESS_AGE.
{
zero := strings.Repeat("A", 43)
in := must(tkr.WithInnerStanzas(func(fk []byte, s []*age.Stanza) ([]*age.Stanza, error) {
s[0] = &age.Stanza{Type: agewrap.StanzaX25519, Args: []string{zero}, Body: make([]byte, 32)}
return s, nil
}))
o := open("INNER_ACCESS_AGE with a share of low order", tkr)
o.dkc, o.identities = in.DKC, in.Identities
add(o)
in = must(tkr.WithInnerStanzas(func(fk []byte, s []*age.Stanza) ([]*age.Stanza, error) {
s[0] = &age.Stanza{Type: agewrap.StanzaX25519, Args: []string{"AAAA"}, Body: make([]byte, 32)}
return s, nil
}))
o = open("INNER_ACCESS_AGE with a share of 3 bytes", tkr)
o.dkc, o.identities = in.DKC, in.Identities
add(o)
in = must(tkr.WithInnerStanzas(func(fk []byte, s []*age.Stanza) ([]*age.Stanza, error) {
return append(s, &age.Stanza{Type: agewrap.StanzaX25519, Args: s[0].Args, Body: s[0].Body}), nil
}))
o = open("INNER_ACCESS_AGE with a share repeated", tkr)
o.dkc, o.identities = in.DKC, in.Identities
add(o)
// The identity of the .dkk opens two stanzas, another identity one.
in = must(tkr.WithInnerStanzas(func(fk []byte, s []*age.Stanza) ([]*age.Stanza, error) {
id := must(tkr.AccessIdentity())
extra := must(testkit.FixedX25519Stanza(fk, id.Recipient(), "datekeys-dart: a second stanza"))
return append(s, extra), nil
}))
o = open("two identities, one of them opens two stanzas", tkr)
o.dkc = in.DKC
o.identities = append([]string{tkr.Identities[0]}, in.Identities...)
add(o)
o = open("one identity opens two stanzas, given twice", tkr)
o.dkc = in.DKC
o.identities = []string{in.Identities[0], in.Identities[0]}
add(o)
}
// Step 14: CONTROL_CBOR sealed again.
{
ctl := func(name string, f *testkit.LoadedFixture, edit func(m map[uint64]any)) opening {
in := must(f.WithControl(edit))
o := open(name, f)
o.dkc = in.DKC
return o
}
add(ctl("CONTROL_CBOR an empty map", tox, func(m map[uint64]any) { clear(m) }))
add(ctl("CONTROL_CBOR without key 3", tox, func(m map[uint64]any) { delete(m, 3) }))
add(ctl("CONTROL_CBOR of version 7", tox, func(m map[uint64]any) { m[1] = uint64(7) }))
add(ctl("CONTROL_CBOR with an unknown key", tox, func(m map[uint64]any) { m[9] = uint64(0) }))
add(ctl("CONTROL_CBOR with a payload_identity of 31 bytes", tox, func(m map[uint64]any) { m[3] = m[3].([]byte)[:31] }))
add(ctl("format 2: CONTROL_CBOR with padding 0", tox2, func(m map[uint64]any) { m[7] = uint64(0) }))
o := ctl("CONTROL_CBOR with an unknown critical extension", tox, func(m map[uint64]any) {
m[4] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1)}}
})
add(o)
o = ctl("CONTROL_CBOR with a known critical extension", tox, func(m map[uint64]any) {
m[4] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1), 2: []byte("ok")}}
})
o.known = testkit.KnownExtensions{{ID: "org.example.lock", Version: 1, ValidData: []byte("ok")}}
add(o)
o = open("every extension rejected: PUBLIC_HEADER and CONTROL_CBOR", tox)
o.rejectAll = "not today"
add(o)
o = open("format 2: every extension rejected", tox2)
o.rejectAll = "not today"
add(o)
}
// Step 15.
{
h := must(testkit.RawHeader([16]byte(must(capsule.DecodeHeader(tox.Parts.Header)).CapsuleID), must(capsule.DecodeHeader(tox.Parts.Header)).DateKey.Compact(), 0))
o := open("PUBLIC_HEADER without its extension, sealed again", tox)
o.dkc = reframe(tox, h, tox.Parts.Sealed, tox.Parts.Payload)
add(o)
}
// Step 17, formats 1 and 2: the age header of PAYLOAD_AGE, its STREAM,
// the padding and the output.
{
payload := tox.Parts.Payload
o := open("PAYLOAD_AGE with its MAC edited", tox)
o.dkc = testkit.Join(tox.Parts.Prelude, tox.Parts.Header, tox.Parts.Sealed, macEdit(payload))
add(o)
o = open("PAYLOAD_AGE without its nonce", tox)
hdr := must(testkit.HeaderLen(payload))
o.dkc = testkit.Join(tox.Parts.Prelude, tox.Parts.Header, tox.Parts.Sealed, payload[:hdr+5])
add(o)
o = open("PAYLOAD_AGE without its STREAM", tox)
o.dkc = testkit.Join(tox.Parts.Prelude, tox.Parts.Header, tox.Parts.Sealed, payload[:hdr+16])
add(o)
o = open("time_only cut in its second STREAM chunk", to)
o.dkc = to.DKC[:len(to.DKC)-1]
add(o)
o = open("time_only with its first STREAM chunk edited", to)
o.dkc = set(to.DKC, len(to.DKC)-20000, to.DKC[len(to.DKC)-20000]^1)
add(o)
for _, name := range []string{"time_only_extensions", "format2_time_only_extensions", "format2_time_only"} {
o = open(name+": the output fails", fx(name))
o.outputFail = true
add(o)
}
l := int(tox2.PayloadLength)
p := int(tox2.PaddedLength)
content := plaintextOf(tox2)
for _, v := range []struct {
name string
plain []byte
}{
{"the content only", content},
{"the content and P - L - 1 zeros", append(bytes.Clone(content), zeros(p-l-1)...)},
{"the content and P - L + 1 zeros", append(bytes.Clone(content), zeros(p-l+1)...)},
{"the content, zeros and a last byte 1", append(append(bytes.Clone(content), zeros(p-l-1)...), 1)},
{"the content, a byte 2 and zeros", append(append(append(bytes.Clone(content), 2), zeros(p-l-1)...))},
{"no plaintext", nil},
{"half the content", content[:l/2]},
{"P zeros", zeros(p)},
} {
add(withPlaintext("format 2: PAYLOAD_AGE holds "+v.name, tox2, v.plain))
}
o = withPlaintext("format 1: PAYLOAD_AGE holds the content and a zero", tox, append(plaintextOf(tox), 0))
add(o)
}
// Step 17, format 3: BODY sealed again.
{
b := readBody3(s3)
l, p := int(s3.PayloadLength), int(s3.PaddedLength)
body := b.bytes()
// nota.txt as CON.txta, of the same length, which breaks R6.
bad := b.withHead(func(h map[uint64]any) { h[5].([]any)[0].(map[uint64]any)[0] = "CON.txta" })
for _, v := range []struct {
name string
plain []byte
}{
{"a plaintext of 5 bytes", body[:5]},
{"a plaintext of 100 bytes", body[:100]},
{"a plaintext cut inside the file", body[:l-2]},
{"a plaintext of P + 1 bytes", testkit.Join(body, zeros(p-l+1))},
{"the last padding byte 1", testkit.Join(body, zeros(p-l-1), []byte{1})},
{"a plaintext of P - 1 bytes", testkit.Join(body, zeros(p-l-1))},
{"an invalid head and a plaintext of P + 1 bytes", testkit.Join(bad, zeros(p-l+1))},
{"an invalid head and a plaintext of P - 1 bytes", testkit.Join(bad, zeros(p-l-1))},
{"an invalid head and a plaintext of P bytes", testkit.Join(bad, zeros(p-l))},
{"BODY and its padding", testkit.Join(body, zeros(p-l))},
} {
add(withPlaintext("format 3: "+v.name, s3, v.plain))
}
o := withBody("format 3: a head with an unknown critical extension", s3, b.withHead(func(h map[uint64]any) {
h[6] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1)}}
}), nil)
add(o)
o = withBody("format 3: a head with an unknown critical extension and a padding byte 1", s3, b.withHead(func(h map[uint64]any) {
h[6] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1)}}
}), func(p []byte) []byte { p[len(p)-1] = 1; return p })
add(o)
o = withBody("format 3: a head with a known critical extension", s3, b.withHead(func(h map[uint64]any) {
h[6] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1), 2: []byte("ok")}}
}), nil)
o.known = testkit.KnownExtensions{{ID: "org.example.lock", Version: 1, ValidData: []byte("ok")}}
add(o)
o = withBody("format 3: a head with a known critical extension with invalid data", s3, b.withHead(func(h map[uint64]any) {
h[6] = []any{map[uint64]any{0: "org.example.lock", 1: uint64(1), 2: []byte("no")}}
}), nil)
o.known = testkit.KnownExtensions{{ID: "org.example.lock", Version: 1, ValidData: []byte("ok")}}
add(o)
o = withBody("format 3: a head with a noncritical extension, rejected", s3, b.withHead(func(h map[uint64]any) {
h[7] = []any{map[uint64]any{0: "org.example.tag", 1: uint64(3), 2: []byte("x")}}
}), nil)
o.rejectAll = "not today"
add(o)
nb := b
nb.content = nil
nh := must(cbortest.UnmarshalMap(b.head))
delete(nh, 5)
nh[3] = "Sin ficheros."
nb.head = must(cbortest.Marshal(nh))
nb.frame.HeadLen = uint32(len(nb.head))
add(withBody("format 3: a head without files and an empty CONTENT", s3, nb.bytes(), nil))
nb.content = []byte{0}
add(withBody("format 3: a head without files and a CONTENT of 1 byte", s3, nb.bytes(), nil))
two := b.withHead(func(h map[uint64]any) {
f0 := h[5].([]any)[0].(map[uint64]any)
sum := sha256.Sum256([]byte("Hola"))
h[5] = []any{
map[uint64]any{0: "a.txt", 1: uint64(len(b.content)), 2: uint64(0), 3: uint64(len(b.content)), 4: f0[4]},
map[uint64]any{0: "b.txt", 1: uint64(4), 2: uint64(len(b.content)), 3: uint64(len(b.content) + 4), 4: sum[:]},
}
})
add(withBody("format 3: two files, the second of another content", s3, append(bytes.Clone(two), []byte("Hole")...), nil))
add(withBody("format 3: two files", s3, append(bytes.Clone(two), []byte("Hola")...), nil))
large := b
large.area = append(bytes.Clone(b.area), zeros(1024)...)
large.frame.AreaLen = uint32(len(large.area))
add(withBody("format 3: an area of 1536 bytes", s3, large.bytes(), nil))
one := b
one.area = append([]byte{0xa0}, zeros(len(b.area)-1)...)
one.frame.SecurityLen = 1
add(withBody("format 3: SECURITY_CBOR of one byte", s3, one.bytes(), nil))
// The sinks and the caller.
for _, at := range []string{"begin", "create 0", "create 1", "write 0", "write 1", "close 0", "close 4", "commit"} {
o = open("format 3: the sink fails at "+at, tree3)
o.sinkFail = at
add(o)
}
o = open("format 3: the caller refuses the verdicts", tree3)
o.refuse = true
add(o)
o = open("format 3: the caller refuses the verdicts of a capsule without files", fx("format3_comment_only"))
o.refuse = true
add(o)
o = open("format 3: the sink fails at begin, with an invalid padding", s3)
o.dkc = must(s3.WithBody(body, func(p []byte) []byte { p[len(p)-1] = 1; return p })).DKC
o.sinkFail = "begin"
add(o)
o = open("format 3, time_and_key: the sink fails at commit", tk3)
o.dkkFile = tk3.DKK
o.sinkFail = "commit"
add(o)
}
var out []Case
for _, o := range os_ {
out = append(out, o.run())
}
return out
}
// dkRound is the canonical dk1_ string of round of Quicknet.
func dkRound(round uint64) string {
j := fmt.Sprintf(`{"version":1,"network":"datekeys:quicknet:v1","round":%d}`, round)
return "dk1_" + base64.RawURLEncoding.EncodeToString([]byte(j))
}
// accessMaterial is the hex of the material of the .dkk dkk.
func accessMaterial(dkk []byte) (string, error) {
k, err := accesskey.Decode(bytes.NewReader(dkk))
if err != nil {
return "", err
}
return hx(k.Material), nil
}
// ---------------------------------------------------------------------------
// The inspection
// inspectCases are the texts of capsule.Inspect for the mutations of
// inspect_differential.json, unique texts in a table, with the result and
// the step of Go checked against the file; and the views of the CLI for
// headers with a public note and with unusable extensions.
func inspectCases() (texts []string, results []int, views []Case) {
var diff struct {
Bases []struct {
File string `json:"file"`
} `json:"bases"`
Mutations []struct {
Base int `json:"base"`
Edits [][]json.RawMessage `json:"edits"`
Result string `json:"result"`
Step int `json:"step"`
} `json:"mutations"`
}
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(fixtureDir, "..", "vectors", "inspect_differential.json"))), &diff); err != nil {
panic(err)
}
index := map[string]int{}
disagree := 0
for i, m := range diff.Mutations {
base := fx(strings.TrimSuffix(diff.Bases[m.Base].File, ".dkc")).DKC
dkc := applyEdits(base, m.Edits)
in, err := capsule.Inspect(bytes.NewReader(dkc), capsule.InspectOptions{Registry: testkit.Registry()})
code, step := "ok", 0
if err != nil {
code = datekeys.Code(err)
step = in.Checks[len(in.Checks)-1].Step
}
if code != m.Result || step != m.Step {
disagree++
fmt.Fprintf(os.Stderr, "inspect_differential.json mutation %d: Go gives %s at step %d, the file says %s at step %d\n", i, code, step, m.Result, m.Step)
}
if err == nil {
results = append(results, -1)
continue
}
t := err.Error()
k, ok := index[t]
if !ok {
k = len(texts)
index[t] = k
texts = append(texts, t)
}
results = append(results, k)
}
fmt.Fprintf(os.Stderr, "inspect_differential.json: %d mutations, %d where Go and the file disagree, %d texts\n", len(diff.Mutations), disagree, len(texts))
// The views of the CLI: headers of time_only_extensions with a note.
tox := fx("time_only_extensions")
h := must(capsule.DecodeHeader(tox.Parts.Header))
note := func(data []byte) extension.Extension {
return extension.Extension{ID: extension.NoteID, Version: 1, Data: data}
}
for _, v := range []struct {
name string
non []extension.Extension
crit []extension.Extension
reg extension.Registry
}{
{"a public note", []extension.Extension{note([]byte("Cartas del viaje a Lisboa"))}, nil, extension.Standard{}},
{"a public note, no registry", []extension.Extension{note([]byte("Cartas del viaje a Lisboa"))}, nil, nil},
{"a public note with <, > and &", []extension.Extension{note([]byte("<b> & </b>"))}, nil, extension.Standard{}},
{"a public note with U+2028, which breaks the rules", []extension.Extension{note([]byte("a\u2028b"))}, nil, extension.Standard{}},
{"a public note with a tab", []extension.Extension{note([]byte("a\tb"))}, nil, extension.Standard{}},
{"a public note without data", []extension.Extension{{ID: extension.NoteID, Version: 1}}, nil, extension.Standard{}},
{"a public note of version 2", []extension.Extension{{ID: extension.NoteID, Version: 2, Data: []byte("a\tb")}}, nil, extension.Standard{}},
{"a public note and another extension", []extension.Extension{note([]byte("\u00d1and\u00fa, a\u00f1o 2026 \U0001f30d")), {ID: "org.example", Version: 1, Data: []byte{1}}}, nil, extension.Standard{}},
{"a public note in the critical array", nil, []extension.Extension{note([]byte("Cartas"))}, extension.Standard{}},
{"every extension rejected", []extension.Extension{note([]byte("Cartas")), {ID: "org.example", Version: 1, Data: []byte{1}}}, nil, rejecting("not today")},
} {
hh := *h
hh.Noncritical, hh.Critical = v.non, v.crit
hb := must(encodeHeaderRaw(&hh))
dkc := reframe(tox, hb, tox.Parts.Sealed, tox.Parts.Payload)
in, err := capsule.Inspect(bytes.NewReader(dkc), capsule.InspectOptions{Registry: testkit.Registry(), Extensions: v.reg})
var b bytes.Buffer
if err := inspectview.New("capsule.dkc", in, err).WriteJSON(&b); err != nil {
panic(err)
}
c := Case{"name": v.name, "dkc": edits(tox.File, dkc), "view": b.String()}
switch v.reg.(type) {
case extension.Standard:
c["registry"] = "standard"
case rejecting:
c["registry"] = "reject_all"
default:
c["registry"] = "none"
}
views = append(views, c)
}
return texts, results, views
}
// encodeHeaderRaw is capsule.EncodeHeader without the rule of the encoders
// of spec §72, which this generator breaks on purpose: the header as the
// reader reads it.
func encodeHeaderRaw(h *capsule.Header) ([]byte, error) {
m := map[uint64]any{0: capsule.HeaderTypeTag, 1: uint64(capsule.HeaderVersion), 2: h.CapsuleID[:], 3: h.DateKey.Compact(), 4: uint64(h.Policy)}
ext := func(xs []extension.Extension) []any {
var out []any
for _, x := range xs {
e := map[uint64]any{0: x.ID, 1: x.Version}
if x.Data != nil {
e[2] = x.Data
}
out = append(out, e)
}
return out
}
if len(h.Critical) > 0 {
m[5] = ext(h.Critical)
}
if len(h.Noncritical) > 0 {
m[6] = ext(h.Noncritical)
}
return cbortest.Marshal(m)
}
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]")
}
out = append(append(out, base[pos:at]...), unhex(insert)...)
pos = at + del
}
return append(out, base[pos:]...)
}
// ---------------------------------------------------------------------------
// The public note
// noteTexts are texts and bytes for extension.CheckNote: the cases of the
// note vectors of the reference, and texts of a fixed seed built from
// pieces that meet or break the rules of spec §24.1 and §29.6.
func noteCases() (checks, notes, standard []Case) {
pieces := []string{
"a", "Cartas", " ", "\t", "\n", "\r", "\u00e9", "\u00f1", "\u65e5\u672c", "\U0001f30d", "\u2764\ufe0f", "\ufe0f", "\u200d", "\u200c",
"\u202e", "\u2066", "\u2028", "\ufeff", "\u200b", "\u00ad", "\U000e0041", "\ufffe", "\x7f", "\x1b", "\u0085",
"\u061c", "\u200e", ".", "-", "~", "\U0001f3f3\ufe0f\u200d\U0001f308", "x",
}
add := func(name string, data []byte) {
c := Case{"name": name, "data": hx(data)}
checks = append(checks, outcome(c, extension.CheckNote(string(data))))
}
for _, c := range []struct {
name string
data []byte
}{
{"a note", []byte("Cartas del viaje a Lisboa")},
{"letters that are not ASCII and an emoji", []byte("\u00d1and\u00fa, a\u00f1o 2026 \U0001F30D")},
{"1024 bytes", []byte(strings.Repeat("a", 1024))},
{"1023 bytes and a letter of 2 bytes", []byte(strings.Repeat("a", 1023) + "\u00e9")},
{"1022 bytes and a letter of 2 bytes", []byte(strings.Repeat("a", 1022) + "\u00e9")},
{"an emoji with VS16", []byte("\u2764\ufe0f")},
{"no byte", []byte{}},
{"1025 bytes", []byte(strings.Repeat("a", 1025))},
{"1025 bytes of 0xff", bytes.Repeat([]byte{0xff}, 1025)},
{"a tab and a byte that is not UTF-8", []byte{9, 0xff}},
{"a tab", []byte("a\tb")},
{"a line feed", []byte("a\nb")},
{"a carriage return", []byte("a\rb")},
{"a space at the start", []byte(" a")},
{"a space at the end", []byte("a ")},
{"a bidi override", []byte("a\u202eb")},
{"a zero width space", []byte("a\u200bb")},
{"a byte order mark at the start", []byte("\ufeffHola")},
{"a byte that is not UTF-8", []byte{'a', 0xff}},
{"the UTF-8 of a lone surrogate", []byte{0xed, 0xa0, 0x80}},
{"a noncharacter", []byte("a\ufffeb")},
{"a tag", []byte("Hola\U000e0041")},
{"the flag of Scotland", []byte("\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f")},
{"the rainbow flag", []byte("\U0001f3f3\ufe0f\u200d\U0001f308")},
{"VS16 after a letter", []byte("a\ufe0f")},
{"ZWJ at the start", []byte("\u200da")},
{"an overlong slash", []byte{0xc0, 0xaf}},
} {
add(c.name, c.data)
}
for i := range 300 {
var sb strings.Builder
for range 1 + rng.IntN(8) {
sb.WriteString(pick(pieces...))
}
data := []byte(sb.String())
if rng.IntN(12) == 0 {
data = append(data, byte(0x80+rng.IntN(0x80)))
}
if rng.IntN(20) == 0 {
data = bytes.Repeat(data, 1+1024/max(1, len(data)))
}
add(fmt.Sprintf("random %d", i), data)
}
// extension.Note and Header.UnusableNote on noncritical arrays.
n := func(v uint64, data []byte) extension.Extension {
return extension.Extension{ID: extension.NoteID, Version: v, Data: data}
}
other := extension.Extension{ID: "org.example", Version: 1, Data: []byte{1}}
for _, v := range []struct {
name string
exts []extension.Extension
}{
{"no extension", nil},
{"a note", []extension.Extension{n(1, []byte("Cartas"))}},
{"another extension and a note", []extension.Extension{other, n(1, []byte("Hola, \u00d1and\u00fa \U0001f30d"))}},
{"a note with a tab", []extension.Extension{n(1, []byte("a\tb"))}},
{"a note without data", []extension.Extension{n(1, nil)}},
{"a note of version 2", []extension.Extension{n(2, []byte("v2"))}},
{"a note of version 2 that breaks the rules", []extension.Extension{n(2, []byte("a\tb"))}},
{"a note that is not UTF-8", []extension.Extension{n(1, []byte{0xff, 0xfe})}},
{"a note with a byte order mark", []extension.Extension{n(1, []byte("\ufeffabc"))}},
{"another extension only", []extension.Extension{other}},
} {
h := capsule.Header{Noncritical: v.exts}
text, ok := h.PublicNote()
c := Case{"name": v.name, "extensions": extsJSON(v.exts), "usable": ok, "unusable": h.UnusableNote()}
if ok {
c["text"] = text
}
notes = append(notes, c)
}
// extension.Standard: CheckNoncriticalIn and CheckWrite of a note.
for _, c := range checks {
data := unhex(c["data"].(string))
e := extension.Extension{ID: extension.NoteID, Version: 1, Data: data}
if len(data) == 0 {
e.Data = nil
}
s := Case{"name": c["name"]}
u := extension.CheckNoncriticalIn(extension.PublicHeader, []extension.Extension{e}, extension.Standard{})
s["unusable"] = unusableJSON(u)
outcome(s, extension.CheckWrite(extension.Standard{}, extension.PublicHeader, extension.Noncritical, []extension.Extension{e}))
standard = append(standard, s)
}
return checks, notes, standard
}
func extsJSON(xs []extension.Extension) [][]any {
out := [][]any{}
for _, x := range xs {
var d any
if x.Data != nil {
d = hx(x.Data)
}
out = append(out, []any{x.ID, x.Version, d})
}
return out
}
// ---------------------------------------------------------------------------
// Heads
// pathPieces build the segments of the paths of the heads.
var pathPieces = []string{
"a", "b", "carta", "nota", ".txt", ".jpg", "fotos", "2025", "\u00e9", "e\u0301", "\u00c9", "\u00f1", "\u65e5\u672c", "\U0001f30d", "\uff5e",
"\U0001f600", "~1", "ABCDEF", "CON", "con", "LPT1", "aux", ".", "..", " ", "\u00a0", "\u3000", "\u200c", "\u200d",
"\ufe0f", "\u202e", "\t", ":", "?", "\u2215", "\uff0f", ".datekeys-x", "x", "K", "\u212a", "\u00df", "SS", "\u0390",
}
func randSegment() string {
var sb strings.Builder
for range 1 + rng.IntN(3) {
sb.WriteString(pick(pathPieces...))
}
return sb.String()
}
func randPath() string {
var segs []string
for range 1 + rng.IntN(3) {
segs = append(segs, randSegment())
}
return strings.Join(segs, "/")
}
var textPieces = []string{
"Para abrir en familia.", "Con cari\u00f1o", " ", "\t", "\n", "\r", "\u00e9", "\U0001f30d", "\u2764\ufe0f", "a\ufe0f", "\u202e",
"\u2028", "\ufeff", "\u200b", "\U000e0041", "\u0085", "\x1b", "\u061c", "\u200e", "\ufffe", "Ana L\u00f3pez", "\u00a0",
}
func randText() string {
var sb strings.Builder
for range 1 + rng.IntN(4) {
sb.WriteString(pick(textPieces...))
}
return sb.String()
}
// headMap is a head of a fixed seed as a map of cbortest, and the paths of
// its files.
func headMap() map[uint64]any {
h := map[uint64]any{0: capsule.HeadTypeTag, 1: uint64(1), 2: randBytes(32)}
if rng.IntN(3) == 0 {
h[3] = pick(randText(), "Para abrir en familia.\nCon cari\u00f1o, desde 2026.", "Hola")
}
if rng.IntN(3) == 0 {
h[4] = pick(randText(), "Ana L\u00f3pez", "Ana")
}
if rng.IntN(5) > 0 {
n := 1 + rng.IntN(5)
var paths []string
for range n {
p := randPath()
if rng.IntN(3) == 0 {
p = pick("a.txt", "b.txt", "carta.txt", "fotos/2025/atardecer.jpg", "fotos/2025/playa.jpg", "notas/a.md")
}
paths = append(paths, p)
}
slices.SortFunc(paths, func(a, b string) int { return strings.Compare(a, b) })
paths = slices.Compact(paths)
var files []any
var end uint64
for _, p := range paths {
size := uint64(pick(0, 1, 37, 3000, rng.IntN(100000)))
f := map[uint64]any{0: p, 1: size, 2: end, 3: end + size, 4: randBytes(32)}
if rng.IntN(2) == 0 {
f[5] = pick(uint64(0), uint64(1790769600), uint64(capsule.MaxMTime), uint64(rng.IntN(1<<31)))
}
end += size
files = append(files, f)
}
h[5] = files
}
ext := func() []any {
var out []any
ids := []string{"a", "org.example", "org.example.tag", "z"}
for _, id := range ids {
if rng.IntN(2) == 0 {
e := map[uint64]any{0: id, 1: uint64(pick(1, 2))}
if rng.IntN(2) == 0 {
e[2] = randBytes(1 + rng.IntN(4))
}
out = append(out, e)
}
}
return out
}
if rng.IntN(5) == 0 {
if x := ext(); len(x) > 0 {
h[6] = x
}
}
if rng.IntN(4) == 0 {
if x := ext(); len(x) > 0 {
h[7] = x
}
}
return h
}
// headMutation changes the head map h, or returns raw bytes for a change
// that cbortest maps cannot hold.
func headMutation(h map[uint64]any) (string, []byte) {
files, _ := h[5].([]any)
file := func() map[uint64]any { return files[rng.IntN(len(files))].(map[uint64]any) }
switch k := rng.IntN(34); {
case k == 0:
return "none", nil
case k == 1:
h[0] = pick("datekeys-heaD", "datekeys-head2", "datekeys-control")
return "type tag", nil
case k == 2:
h[1] = uint64(pick(0, 2, 3))
return "version", nil
case k == 3:
delete(h, uint64(pick(0, 1, 2)))
return "a required key removed", nil
case k == 4:
h[uint64(pick(8, 9, 24, 1000))] = uint64(1)
return "an unknown key", nil
case k == 5:
h[2] = randBytes(pick(0, 31, 33))
return "a salt of another length", nil
case k == 6:
h[3] = pick("", "a\u202eb", "Hola\nmundo", "a\x00b")
return "a comment", nil
case k == 7:
h[4] = pick("", "Ana\nL\u00f3pez", " Ana", "Ana\t")
return "a declared author", nil
case k == 8:
h[3] = uint64(7)
return "a comment that is not text", nil
case k == 9 && len(files) > 1:
i := rng.IntN(len(files) - 1)
files[i], files[i+1] = files[i+1], files[i]
return "two files swapped", nil
case k == 10 && len(files) > 0:
h[5] = append(files, files[len(files)-1])
return "the last file repeated", nil
case k == 11 && len(files) > 0:
file()[0] = pick("", "\x00", "a//b", "/a")
return "a path at or past R1", nil
case k == 12 && len(files) > 0:
f := file()
f[1] = f[1].(uint64) + 1
return "a size one too large", nil
case k == 13 && len(files) > 0:
f := file()
f[2] = f[2].(uint64) + 1
return "a start one too large", nil
case k == 14 && len(files) > 0:
f := file()
f[3] = uint64(pick(0, 8936830510563329, 9007199254740992))
return "an end out of range", nil
case k == 15 && len(files) > 0:
file()[5] = uint64(pick(253402300800, 1<<53-1))
return "an mtime past 9999", nil
case k == 16 && len(files) > 0:
file()[4] = randBytes(pick(0, 31, 33))
return "a SHA-256 of another length", nil
case k == 17 && len(files) > 0:
delete(file(), uint64(pick(0, 1, 2, 3, 4)))
return "a key of a file removed", nil
case k == 18 && len(files) > 0:
file()[uint64(pick(6, 7, 100))] = uint64(0)
return "an unknown key in a file", nil
case k == 19:
h[5] = []any{}
return "an empty array of files", nil
case k == 20:
h[uint64(pick(6, 7))] = []any{}
return "an empty extension array", nil
case k == 21:
x := map[uint64]any{0: "org.example", 1: uint64(1)}
h[6], h[7] = []any{x}, []any{x}
return "an extension in both arrays", nil
case k == 22:
h[6] = []any{map[uint64]any{0: "b", 1: uint64(1)}, map[uint64]any{0: "a", 1: uint64(1)}}
return "extensions out of order", nil
case k == 23:
h[7] = []any{map[uint64]any{0: "org.example", 1: uint64(1), 2: []byte{}}}
return "extension data present but empty", nil
case k == 24 && len(files) > 0:
file()[0] = "a/" + randSegment()
return "a path in a", nil
case k == 25:
h[5] = []any{
map[uint64]any{0: "\uff5e", 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: randBytes(32)},
map[uint64]any{0: "\U0001f600", 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: randBytes(32)},
}
if rng.IntN(2) == 0 {
h[5] = []any{h[5].([]any)[1], h[5].([]any)[0]}
return "U+1F600 before U+FF5E, UTF-16 order", nil
}
return "U+FF5E before U+1F600, UTF-8 order", nil
case k == 26:
b := must(cbortest.Marshal(h))
return "a byte more", append(b, 0)
case k == 27:
b := must(cbortest.Marshal(h))
return "cut short", b[:len(b)-1-rng.IntN(min(len(b)-1, 8))]
case k == 28:
b := must(cbortest.Marshal(h))
i := rng.IntN(len(b))
b[i] ^= byte(1 << rng.IntN(8))
return "a bit flipped", b
case k == 29:
// The keys out of order: keys 0 and 1 swapped.
var pairs cbortest.Pairs
pairs = append(pairs, uint64(1), h[1], uint64(0), h[0])
for k := uint64(2); k < 8; k++ {
if v, ok := h[k]; ok {
pairs = append(pairs, k, v)
}
}
return "keys 0 and 1 swapped", must(cbortest.Marshal(pairs))
case k == 30:
// The version as a byte string... layer 2.
h[1] = []byte{1}
return "a version that is not an integer", nil
case k == 31:
var tags []any
for i := range 3 {
tags = append(tags, map[uint64]any{0: fmt.Sprintf("f%d", i), 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: randBytes(32)})
}
h[5] = tags
return "three files of 0 bytes", nil
case k == 32:
h[5] = []any{map[uint64]any{0: "A", 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: randBytes(32)},
map[uint64]any{0: "a/b", 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: randBytes(32)}}
return "a file A and a folder a", nil
default:
return "none", nil
}
}
func headCases() (decode, encode []Case) {
reg := testkit.KnownExtensions{{ID: "a", Version: 1, ValidData: nil}, {ID: "org.example", Version: 2, ValidData: []byte{1}}}
for range 350 {
h := headMap()
kind, raw := headMutation(h)
b := raw
if b == nil {
var err error
if b, err = cbortest.Marshal(h); err != nil {
continue
}
}
c := Case{"kind": kind, "hex": hx(b)}
_, err := capsule.DecodeHead(b, nil)
outcome(c, err)
r := Case{}
_, err = capsule.DecodeHead(b, reg)
outcome(r, err)
if r["result"] != c["result"] || r["text"] != c["text"] {
c["with_registry"] = r
}
decode = append(decode, c)
}
// The limits of the texts and of R1, once each.
for _, v := range []struct {
kind string
key uint64
val string
}{
{"a comment of 16384 bytes", 3, strings.Repeat("a", 16384)},
{"a comment of 16385 bytes", 3, strings.Repeat("a", 16385)},
{"a declared author of 256 bytes", 4, strings.Repeat("a", 256)},
{"a declared author of 257 bytes", 4, strings.Repeat("a", 257)},
{"a path of 1024 bytes in 5 segments", 5, strings.Repeat(strings.Repeat("b", 204)+"/", 4) + strings.Repeat("b", 204)},
{"a path of 1025 bytes", 5, strings.Repeat("a/", 512) + "a"},
{"a path of 1024 bytes in 512 segments", 5, strings.Repeat("a/", 511) + "aa"},
} {
h := map[uint64]any{0: capsule.HeadTypeTag, 1: uint64(1), 2: make([]byte, 32)}
if v.key == 5 {
h[5] = []any{map[uint64]any{0: v.val, 1: uint64(0), 2: uint64(0), 3: uint64(0), 4: make([]byte, 32)}}
} else {
h[v.key] = v.val
}
b := must(cbortest.Marshal(h))
c := Case{"kind": v.kind, "hex": hx(b)}
_, err := capsule.DecodeHead(b, nil)
decode = append(decode, outcome(c, err))
}
// EncodeHead of values: the heads of the decode cases that decode,
// with their extensions in another order and with errors of their own.
for _, c := range decode {
if c["result"] != "ok" || len(encode) >= 160 {
continue
}
h := must(capsule.DecodeHead(unhex(c["hex"].(string)), nil))
switch rng.IntN(4) {
case 0:
slices.Reverse(h.Critical)
slices.Reverse(h.Noncritical)
case 1:
h.Noncritical = append(h.Noncritical, extension.Extension{ID: "dup", Version: 1}, extension.Extension{ID: "dup", Version: 2})
case 2:
h.Critical = append(h.Critical, extension.Extension{ID: "both", Version: 1})
h.Noncritical = append(h.Noncritical, extension.Extension{ID: "both", Version: 1})
}
e := Case{"head": headJSON(h)}
b, err := capsule.EncodeHead(h)
outcome(e, err)
if err == nil {
e["hex"] = hx(b)
}
encode = append(encode, e)
}
return decode, encode
}
func headJSON(h *capsule.Head) Case {
files := [][]any{}
for _, f := range h.Files {
var mtime any
if f.HasMTime {
mtime = f.MTime
}
files = append(files, []any{f.Path, f.Size, f.Start, f.End, hx(f.SHA256[:]), mtime})
}
return Case{"salt": hx(h.Salt[:]), "comment": h.Comment, "author": h.Author, "files": files,
"critical": extsJSON(h.Critical), "noncritical": extsJSON(h.Noncritical)}
}
// ---------------------------------------------------------------------------
// Output
func enc(v any) string {
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetEscapeHTML(false)
if err := e.Encode(v); err != nil {
panic(err)
}
// No apostrophe, so that the text is a raw string of Dart.
return strings.ReplaceAll(strings.TrimSuffix(b.String(), "\n"), "'", `'`)
}
// render is the text of one file: the object with its fields and sections,
// one case per line.
func render(source, description string, fields []string, values map[string]any, sections []string, cases map[string][]Case) string {
var sb strings.Builder
sb.WriteString("{\n")
sb.WriteString(` "spec": ` + enc(specVersion) + ",\n")
sb.WriteString(` "generator": "tool/open_go_vectors.go",` + "\n")
sb.WriteString(` "source": ` + enc(source) + ",\n")
sb.WriteString(` "description": ` + enc(description))
for _, f := range fields {
sb.WriteString(",\n " + enc(f) + ": " + enc(values[f]))
}
for _, s := range sections {
sb.WriteString(",\n " + enc(s) + ": [")
for i, c := range cases[s] {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(c))
}
sb.WriteString("\n ]")
}
sb.WriteString("\n}\n")
return sb.String()
}
func main() {
testdata := flag.String("testdata", "", "the synced testdata/ of datekeys-dart")
outDir := flag.String("out", "", "where the vectors go")
src := flag.String("source", "", "the commit of datekeys-go")
flag.Parse()
fixtureDir = filepath.Join(*testdata, "fixtures")
type file struct {
name, description string
fields []string
values map[string]any
sections []string
cases map[string][]Case
}
var files []file
decode, encode := headCases()
files = append(files, file{name: "heads", description: "HEAD_CBOR of a fixed seed, valid and broken in each layer of spec §69.1, through capsule.DecodeHead with no registry: the result, the code and the text; with_registry when the registry of the extensions a version 1 and org.example version 2 with data 01 (testkit.KnownExtensions) gives another. encode: capsule.EncodeHead of the decoded heads, their extensions reordered, repeated or in both arrays.",
sections: []string{"decode", "encode"}, cases: map[string][]Case{"decode": decode, "encode": encode}})
checks, notes, standard := noteCases()
files = append(files, file{name: "notes", description: "The public note of spec §24.1: extension.CheckNote on the string of the bytes data; extension.Note and Header.UnusableNote of noncritical arrays (usable, text, unusable); and extension.Standard, in the order of check, on a note of version 1 with its bytes, absent data when empty: CheckNoncriticalIn of PUBLIC_HEADER (unusable, as [id, version, text]) and CheckWrite in its noncritical array (result and text).",
sections: []string{"check", "note", "standard"}, cases: map[string][]Case{"check": checks, "note": notes, "standard": standard}})
texts, results, views := inspectCases()
files = append(files, file{name: "inspect", description: "texts: the texts of the errors of capsule.Inspect, and results: for each mutation of testdata/vectors/inspect_differential.json, in its order, the index of its text in texts, -1 when steps 1 to 8 pass. views: the exact output of datekeys inspect -json (internal/inspectview) for time_only_extensions with another PUBLIC_HEADER, with the registry standard (extension.Standard), none, or reject_all, which knows every extension and rejects all data with not today.",
fields: []string{"texts", "results"}, values: map[string]any{"texts": texts, "results": results},
sections: []string{"views"}, cases: map[string][]Case{"views": views}})
fixtureOpenings := fixtureCases()
steps := stepCases()
files = append(files, file{name: "cases", description: "capsule.Open of a .dkc, as edits of a fixture, given the release (null: none), or a source that fails with source_error (its text and its code, none for a plain error); the clock now; the registry of profiles, empty when registry is empty; the extensions known at (id, version) with valid data valid_data (testkit.KnownExtensions), or reject_all, a registry that knows every extension and rejects all data with that text; the age identities, the .dkk decoded before (dkk, with its material replaced by dkk_material) or the .dkk still encoded (dkk_file); a sink that fails at sink_fail (begin, create i, write i, close i or commit, with the text no and the call); an output that fails with disk full when output_fail; and accept, refuse to refuse every verdict with not trusted. The outcome: result (ok, refused, or the code), text, the checks as [step, name, ok, detail, code], the release requests, the state of the sink, the content (length and SHA-256) or the files ([path, size, SHA-256 of what the sink received]) of a capsule that opens, and the unusable extensions of each object as [id, version, text].",
sections: []string{"fixtures", "steps"}, cases: map[string][]Case{"fixtures": fixtureOpenings, "steps": steps}})
writeDart(*outDir, *src, files[0].cases, files[1].cases, views, texts, results, fixtureOpenings, steps)
for _, f := range files {
text := render(*src, f.description, f.fields, f.values, f.sections, f.cases)
if err := os.WriteFile(filepath.Join(*outDir, "open_"+f.name+".json"), []byte(text), 0o644); err != nil {
panic(err)
}
var counts []string
for _, s := range f.sections {
counts = append(counts, fmt.Sprintf("%s %d", s, len(f.cases[s])))
}
fmt.Fprintf(os.Stderr, "open_%s.json: %d bytes, %s\n", f.name, len(text), strings.Join(counts, ", "))
}
}
// embedded are the fixtures whose bytes test/vectors/open_vectors.g.dart
// holds, for the tests compiled to JavaScript: the small ones of each format
// and policy.
var embedded = []string{"time_only_extensions", "time_and_key_portable", "time_and_key_recipients",
"format2_time_only_extensions", "format2_time_and_key_recipients", "format3_single", "format3_time_and_key_portable"}
// writeDart writes test/vectors/open_vectors.g.dart: the embedded fixtures,
// the cases of open_cases.json on them, every second one of the steps, every eighth head, every second
// note, the views, and every sixth mutation of inspect_differential.json
// of the embedded fixtures with its text.
func writeDart(dir, source string, heads, notes map[string][]Case, views []Case, texts []string, results []int, fixtures, steps []Case) {
bases := map[string]bool{}
fx := map[string]string{}
records := map[string]Case{}
for _, name := range embedded {
f := testkitFixture(name)
bases[f.File] = true
fx[f.File] = hx(f.DKC)
if f.DKK != nil {
fx[f.AccessKeyFile] = hx(f.DKK)
}
rec := Case{"format": f.Format, "release": Case{"round": f.Published.Round, "signature": hx(f.Published.Signature)},
"unlock_at": f.UnlockAt, "payload_identity": f.PayloadIdentity, "payload_length": f.PayloadLength}
if f.AccessKeyFile != "" {
rec["access_key_file"] = f.AccessKeyFile
}
if len(f.Identities) > 0 {
rec["identities"] = f.Identities
}
records[f.File] = rec
}
sub := func(cs []Case, keep func(i int, c Case) bool) []Case {
var out []Case
for i, c := range cs {
if keep(i, c) {
out = append(out, c)
}
}
return out
}
onBase := func(_ int, c Case) bool { return bases[c["dkc"].(Case)["base"].(string)] }
stepsOnBase := sub(steps, onBase)
cases := map[string][]Case{"fixtures": sub(fixtures, onBase), "steps": sub(stepsOnBase, func(i int, _ Case) bool { return i%2 == 0 })}
h := map[string][]Case{
"decode": sub(heads["decode"], func(i int, _ Case) bool { return i%8 == 0 }),
"encode": sub(heads["encode"], func(i int, _ Case) bool { return i%4 == 0 }),
}
n := map[string][]Case{
"check": sub(notes["check"], func(i int, _ Case) bool { return i%2 == 0 }),
"note": notes["note"],
"standard": sub(notes["standard"], func(i int, _ Case) bool { return i%2 == 0 }),
}
// The mutations of inspect_differential.json of the embedded fixtures.
var diff struct {
Bases []struct {
File string `json:"file"`
} `json:"bases"`
Mutations []struct {
Base int `json:"base"`
Edits json.RawMessage `json:"edits"`
Result string `json:"result"`
Step int `json:"step"`
} `json:"mutations"`
}
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(fixtureDir, "..", "vectors", "inspect_differential.json"))), &diff); err != nil {
panic(err)
}
var mutations []Case
for i, m := range diff.Mutations {
file := diff.Bases[m.Base].File
if !bases[file] || i%6 != 0 {
continue
}
c := Case{"index": i, "base": file, "edits": m.Edits, "result": m.Result}
if results[i] >= 0 {
c["step"], c["text"] = m.Step, texts[results[i]]
}
mutations = append(mutations, c)
}
var b strings.Builder
b.WriteString("// Generated by tool/open_go_vectors.go: the fixtures that the cases below\n")
b.WriteString("// edit, and a part of test/vectors/open_*.json, for the tests that also run\n")
b.WriteString("// compiled to JavaScript, where no file can be read. Do not edit.\n\n")
constant := func(name, doc, text string) {
if strings.Contains(text, "'''") {
panic("a raw string of Dart cannot hold '''")
}
fmt.Fprintf(&b, "/// %s\nconst %s = r'''\n%s''';\n\n", doc, name, text)
}
constant("openFixturesJson", "The bytes of the fixtures that the cases edit, in hexadecimal.", enc(fx)+"\n")
constant("openRecordsJson", "What the records of those fixtures say of them: the format, the release, the round time, I_PAYLOAD, L, and their credentials.", enc(records)+"\n")
constant("openCasesJson", "Part of test/vectors/open_cases.json: its cases on those fixtures.",
render(source, "Part of open_cases.json.", nil, nil, []string{"fixtures", "steps"}, cases))
constant("openHeadsJson", "Part of test/vectors/open_heads.json.", render(source, "Part of open_heads.json.", nil, nil, []string{"decode", "encode"}, h))
constant("openNotesJson", "Part of test/vectors/open_notes.json.", render(source, "Part of open_notes.json.", nil, nil, []string{"check", "note", "standard"}, n))
constant("openInspectJson", "The views of test/vectors/open_inspect.json, and every sixth mutation of testdata/vectors/inspect_differential.json of those fixtures, with the text of Go.",
render(source, "Part of open_inspect.json.", nil, nil, []string{"views", "mutations"}, map[string][]Case{"views": views, "mutations": mutations}))
out := strings.TrimSuffix(b.String(), "\n")
if err := os.WriteFile(filepath.Join(dir, "open_vectors.g.dart"), []byte(out), 0o644); err != nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "open_vectors.g.dart: %d bytes, %d cases, %d mutations\n", len(out), len(cases["fixtures"])+len(cases["steps"]), len(mutations))
}
func testkitFixture(name string) *testkit.LoadedFixture { return fx(name) }

Powered by TurnKey Linux.