Format 3, step 6c: the vectors of paths, keys, heads and security

The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:

- paths.json: 83 paths with the result of the rules of one entry, the
  violation worded as every implementation must word it, and 16 trees,
  the paths of a head and the result of decoding it: U+00A0, accepted,
  and U+3000, R6c, at both ends of a segment; best-fit, full-width
  forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
  tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
  times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
  in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
  accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
  and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
  among them the entries F of CaseFolding, the dotless i, the Kelvin
  and Angstrom signs, Cherokee, Hangul and the whitelist dropped
  before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
  with the violation of each ERR_HEAD_INVALID; they add comments with
  tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
  S2, among them key 2 that is not a byte string, a key 4, a byte more,
  alg 0, an empty key with the seal intact, and a seal that breaks its
  schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.

A test replays every committed vector through the implementation, and
another checks that the files are current.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.10
dev 1 week ago
parent e070f891ea
commit db862d5524

@ -565,6 +565,23 @@ func schemaVectors() ([]schemaVector, error) {
pc2("schema version 1 with keys 6 and 7", with(c2, 1, uint64(1)), unsup)
pc2("schema version 3", with(c2, 1, uint64(3)), unsup)
// CONTROL_CBOR of a format 3 capsule, schema version 3 (spec §31): the
// keys of version 2, where L is the length of BODY (spec §29.2).
c3 := with(c2, 1, uint64(capsule.Format3))
pc3 := func(name string, v any, want string) {
out = append(out, schemaVector{block: SchemaControl, schema: SchemaControl, format: 3, name: "format 3: " + name, value: v, want: want})
}
pc3("minimal control, 103 bytes, L = 0", c3, ResultOK)
pc3("both extension arrays", with(c3, 4, []any{ext("org.example.a", 1)}, 5, []any{ext("org.example.b", 1, []byte("x"))}), ResultOK)
pc3("payload_length 84078 and padding 1", with(c3, 6, payloadLength(84078), 7, uint64(capsule.Bloque256)), ResultOK)
pc3("payload_length L_MAX + 1", with(c3, 6, payloadLength(capsule.MaxPayloadLength+1)), nc)
pc3("missing key 6, payload_length", with(c3, 6, nil), nc)
pc3("missing key 7, padding", with(c3, 7, nil), nc)
pc3("schema version 3 without keys 6 and 7", with(c, 1, uint64(3)), nc)
pc3("schema version 2, a valid format 2 control", c2, unsup)
pc3("schema version 1, a valid format 1 control", c, unsup)
pc3("schema version 4", with(c3, 1, uint64(4)), unsup)
// .dkk body (spec §41): keys 0 to 5, optional 6, 7 and 8.
k := map[uint64]any{0: accesskey.TypeTag, 1: uint64(accesskey.SchemaVersion), 2: fill(0x33, 16), 3: fill(0x44, 16), 4: accesskey.TypeX25519, 5: fill(0x55, 32)}
pk := func(name string, v any, want string) { add(SchemaDKKBody, SchemaDKKBody, name, v, want) }

@ -0,0 +1,577 @@
package testkit
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/internal/cbortest"
"g.activething.com/go/DateKeys/internal/pathrule"
)
// The vectors of format 3 (spec §67): testdata/vectors/paths.json,
// path_fold.json, head_schema.json and security.json.
// PathVectorFile is testdata/vectors/paths.json: the rules of spec §29.5 for
// one path, and for the paths of a head.
type PathVectorFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
UnicodeVersion string `json:"unicode_version"`
TablesDigest string `json:"tables_digest"`
Paths []PathVector `json:"paths"`
Trees []TreeVector `json:"trees"`
}
// PathVector is a path and the result of the rules of one entry, R2 to R6c
// and R10 (spec §29.5): ResultOK, or the violation as every implementation
// words it, such as "R4: segment 1: control U+0009".
type PathVector struct {
Name string `json:"name"`
Path string `json:"path"`
Result string `json:"result"`
}
// TreeVector is the paths of the files of a head, of 0 bytes each, and the
// result of decoding that head (spec §29.4, §29.5): ResultOK or its code, R1
// and R8 being of layer 3 and the other rules of layer 4. Detail is the
// violation of an ERR_HEAD_INVALID, as for HeadVector.
type TreeVector struct {
Name string `json:"name"`
Paths []string `json:"paths"`
Result string `json:"result"`
Detail string `json:"detail,omitempty"`
}
// PathFoldFile is testdata/vectors/path_fold.json: the key of R7 of a
// segment, NFD(fold(NFD(s'))) with s' the segment without the whitelist of
// R4 (spec §29.5, §29.5.1).
type PathFoldFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
UnicodeVersion string `json:"unicode_version"`
TablesDigest string `json:"tables_digest"`
Keys []FoldVector `json:"keys"`
}
// FoldVector is a segment, its NFD and its key of R7.
type FoldVector struct {
Name string `json:"name"`
Segment string `json:"segment"`
NFD string `json:"nfd"`
Key string `json:"key"`
}
// HeadSchemaFile is testdata/vectors/head_schema.json: heads and the result
// of decoding them with no extension known (spec §29.4 to §29.6, §69.1).
type HeadSchemaFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
Heads []HeadVector `json:"heads"`
}
// HeadVector is HEAD_CBOR and its result: ResultOK or the normative code.
// Detail is the violation of an ERR_HEAD_INVALID, as every implementation
// words it: "comment: text: bidirectional control U+202E", "file 1: R3:
// segment 1: the segment is two dots" or "R7: path 2 collides with path 1 in
// segment 1".
type HeadVector struct {
Name string `json:"name"`
Hex string `json:"hex"`
Result string `json:"result"`
Detail string `json:"detail,omitempty"`
}
// SecurityVectorFile is testdata/vectors/security.json: SECURITY_CBOR and
// its verdicts (spec §29.3, §29.7).
type SecurityVectorFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
Vectors []SecurityVector `json:"vectors"`
}
// SecurityVector is SECURITY_CBOR, exactly its SECURITY_LEN bytes, and the
// verdicts of the signature and of the seal.
type SecurityVector struct {
Name string `json:"name"`
Hex string `json:"hex"`
Signature string `json:"signature"`
Seal string `json:"seal"`
}
// HeadDetail is the violation of an ERR_HEAD_INVALID of capsule.DecodeHead,
// without its prefix and its code, and "" for any other error.
func HeadDetail(err error) string {
if !errors.Is(err, datekeys.ErrHeadInvalid) {
return ""
}
s := strings.TrimPrefix(err.Error(), "capsule: head: ")
return strings.TrimSuffix(s, ": "+datekeys.ErrHeadInvalid.Code())
}
// PathResult is the result of the rules of one entry for path.
func PathResult(path string) string {
if err := pathrule.CheckPath(path); err != nil {
return err.Error()
}
return ResultOK
}
// emptySHA256 is the SHA-256 of a file of 0 bytes.
var emptySHA256 = sha256.Sum256(nil)
// head3 returns a head map with a zero salt and the given keys; a nil value
// removes the key.
func head3(kv ...any) map[uint64]any {
h := map[uint64]any{0: capsule.HeadTypeTag, 1: uint64(capsule.HeadVersion), 2: make([]byte, capsule.SaltSize)}
for i := 0; i < len(kv); i += 2 {
k := uint64(kv[i].(int))
if kv[i+1] == nil {
delete(h, k)
} else {
h[k] = kv[i+1]
}
}
return h
}
// entry3 is a file entry of a head.
func entry3(path any, size, start, end uint64) map[uint64]any {
return map[uint64]any{0: path, 1: size, 2: start, 3: end, 4: emptySHA256[:]}
}
// TreeHead is HEAD_CBOR with a file of 0 bytes for each path, in the order
// given.
func TreeHead(paths []string) ([]byte, error) {
var files []any
for _, p := range paths {
files = append(files, entry3(p, 0, 0, 0))
}
return cbortest.Marshal(head3(5, files))
}
// HeadResult decodes HEAD_CBOR with no extension known and returns its
// result and, for ERR_HEAD_INVALID, its violation.
func HeadResult(b []byte) (string, string) {
_, err := capsule.DecodeHead(b, nil)
return Result(err), HeadDetail(err)
}
// SecurityResult returns the verdicts of SECURITY_CBOR.
func SecurityResult(b []byte) (string, string) {
v := capsule.EvaluateSecurity(b)
return string(v.Signature), string(v.Seal)
}
// check compares got with want: "*" accepts anything, and otherwise want
// is got or its start up to a ": ".
func check(what, name, got, want string) error {
if want == "*" || got == want || strings.HasPrefix(got, want+": ") {
return nil
}
return fmt.Errorf("%s %q: got %q, want %q", what, name, got, want)
}
// PathVectors computes testdata/vectors/paths.json, and fails if a vector
// does not get the result it is written for.
func PathVectors() (PathVectorFile, error) {
f := PathVectorFile{
Spec: SpecVersion,
Description: "Paths of a format 3 head (spec §29.5) with the Unicode 18.0.0 and best-fit tables of §29.5.1, generated by the reference implementation. " +
"paths: one path and the rules of one entry, R2 to R6c and R10; trees: the paths of a head, of 0 bytes each, and the result of decoding it. See testdata/README.md.",
UnicodeVersion: pathrule.UnicodeVersion,
TablesDigest: pathrule.TablesDigest,
}
var errs []error
for _, v := range pathCases() {
got := PathResult(v.path)
if err := check("path", v.name, got, v.want); err != nil {
errs = append(errs, err)
}
f.Paths = append(f.Paths, PathVector{Name: v.name, Path: v.path, Result: got})
}
for _, v := range treeCases() {
b, err := TreeHead(v.paths)
if err != nil {
return f, err
}
got, detail := HeadResult(b)
full := got
if detail != "" {
full += ": " + detail
}
if err := check("tree", v.name, full, v.want); err != nil {
errs = append(errs, err)
}
f.Trees = append(f.Trees, TreeVector{Name: v.name, Paths: v.paths, Result: got, Detail: detail})
}
return f, errors.Join(errs...)
}
type pathCase struct{ name, path, want string }
func pathCases() []pathCase {
scotland := "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f"
return []pathCase{
{"a file", "nota.txt", ResultOK},
{"a file in two folders", "fotos/2025/playa.jpg", ResultOK},
{"non-ASCII letters", "música/canción.txt", ResultOK},
{"U+00BF, which bestfit1250 maps to '?'", "¿Qué es esto.jpg", ResultOK},
{"U+00A7, which bestfit874 maps to a C0 control", "§ 3 contrato.pdf", ResultOK},
{"U+2665, which bestfit874 maps to a C0 control", "Para ti ♥.jpg", ResultOK},
{"U+2192, which bestfit1253 maps to '>'", "Madrid → Lisboa", ResultOK},
{"U+00A0 at the start of a segment: no table maps it to ASCII", "\u00a0a", ResultOK},
{"U+00A0 at the end of a segment", "a\u00a0", ResultOK},
{"U+3000 at the start: bestfit1250 and others map it to U+0020", "\u3000a", "R6c"},
{"U+3000 at the end", "a\u3000", "R6c"},
{"CON.txt in full-width forms", "\uff23\uff2f\uff2e.txt", "R6c"},
{"U+2216 SET MINUS", "a\u2216b", "R6c"},
{"U+2236 RATIO", "a\u2236b", "R6c"},
{"U+00A5, which cp932 maps to '\\'", "a\u00a5b", "R6c"},
{"U+20A9, which cp949 maps to '\\'", "a\u20a9b", "R6c"},
{"U+00B4, which cp1253 maps to '/'", "a\u00b4b", "R6c"},
{"full-width solidus", "a\uff0fb", "R6c"},
{"full-width reverse solidus", "a\uff3cb", "R6c"},
{"full-width colon", "a\uff1ab", "R6c"},
{"two full-width full stops", "\uff0e\uff0e", "R6c"},
{"8.3 alias", "ABCDEF~1", "R6b"},
{"8.3 alias with an extension", "ABCDEF~1.TXT", "R6b"},
{"~1 alone", "~1", "R6b"},
{"8.3 alias with a non-ASCII letter", "\u00c4~1.txt", "R6b"},
{"nine characters before ~1", "ABCDEFGHI~1", ResultOK},
{"~1 inside a name", "a~1b", ResultOK},
{"~2023 in a long name", "report~2023.txt", ResultOK},
{"unassigned U+0378", "a\u0378b", "R4"},
{"noncharacter U+FFFE", "a\ufffeb", "R4"},
{"U+206A", "a\u206ab", "R4"},
{"U+206F", "a\u206fb", "R4"},
{"soft hyphen U+00AD", "a\u00adb", "R4"},
{"U+034F COMBINING GRAPHEME JOINER", "a\u034fb", "R4"},
{"U+200B ZERO WIDTH SPACE", "a\u200bb", "R4"},
{"U+2060 WORD JOINER", "a\u2060b", "R4"},
{"U+3164 HANGUL FILLER", "a\u3164b", "R4"},
{"U+FEFF", "a\ufeffb", "R4"},
{"U+202E RIGHT-TO-LEFT OVERRIDE", "a\u202eb", "R4"},
{"tag U+E0041", "a\U000e0041", "R4"},
{"variation selector VS17", "\U0001f600\U000e0100", "R4"},
{"the flag of Scotland, with tags", scotland, "R4"},
{"U+F03A of the private use area", "informe\uf03aanexo", "R4"},
{"TAB", "a\u0009b", "R4"},
{"U+2028 LINE SEPARATOR", "a\u2028b", "R4"},
{"':'", "a:b", "R4"},
{"'\\'", "a\\b", "R4"},
{"'?'", "a?b", "R4"},
{"'\"'", "a\"b", "R4"},
{"'*', '<', '>' and '|'", "a*b<c>d|e", "R4"},
{"a dot and ZWJ", ".\u200d", "R3"},
{"a segment of ZWJ alone", "\u200d", "R3"},
{"two dots", "..", "R3"},
{"one dot inside", "a/./b", "R3"},
{"a segment of 256 bytes", strings.Repeat("a", 256), "R3"},
{"a segment of 255 bytes", strings.Repeat("a", 255), ResultOK},
{"127 times U+0390: 381 UTF-16 units after NFD", strings.Repeat("\u0390", 127), "R3"},
{"85 times U+0390: 255 UTF-16 units after NFD", strings.Repeat("\u0390", 85), ResultOK},
{"VS16 after U+2764, which admits it", "\u2764\ufe0f.txt", ResultOK},
{"VS16 after a", "a\ufe0f", "R4b"},
{"ZWJ at the start", "\u200da", "R4b"},
{"ZWJ at the end", "a\u200d", "R4b"},
{"two ZWJ in a row", "a\u200d\u200db", "R4b"},
{"ZWNJ and ZWJ in a row", "a\u200c\u200db", "R4b"},
{"ZWNJ inside a name", "ab\u200cc", ResultOK},
{"the rainbow flag", "\U0001f3f3\ufe0f\u200d\U0001f308", ResultOK},
{"a family, joined with ZWJ", "\U0001f468\u200d\U0001f469\u200d\U0001f467", ResultOK},
{"a leading slash", "/a", "R2"},
{"two slashes", "a//b", "R2"},
{"a trailing slash", "a/", "R2"},
{"33 segments", strings.Repeat("a/", 32) + "a", "R2"},
{"a leading space", " a", "R5"},
{"a trailing space", "a ", "R5"},
{"a trailing dot", "a.", "R5"},
{"CON.txt", "CON.txt", "R6"},
{"con", "con", "R6"},
{"Aux with a space before the dot", "Aux .log", "R6"},
{"COM with a superscript one", "COM\u00b9", "R6"},
{"lpt9.doc", "lpt9.doc", "R6"},
{"CONIN$", "CONIN$", "R6"},
{".datekeys-x at the first level", ".datekeys-x", "R10"},
{".DateKeys-X at the first level, compared by its key", ".DateKeys-X/b", "R10"},
{".datekeys-x at another level", "a/.datekeys-x", ResultOK},
}
}
type treeCase struct {
name string
paths []string
want string
}
func treeCases() []treeCase {
nc, hi := datekeys.ErrNonCanonicalCBOR.Code(), datekeys.ErrHeadInvalid.Code()
return []treeCase{
{"three files in two folders", []string{"a/b", "a/c", "d"}, ResultOK},
{"U+FF5E before U+1F600: UTF-8 byte order", []string{"\uff5e", "\U0001f600"}, ResultOK},
{"U+1F600 before U+FF5E: UTF-16 order, not UTF-8 byte order", []string{"\U0001f600", "\uff5e"}, nc},
{"b and a, in that order", []string{"b", "a"}, nc},
{"b/.. and a: R8 comes first, in layer 3", []string{"b/..", "a"}, nc},
{"the same path twice", []string{"a", "a"}, nc},
{"a path of 1025 bytes", []string{strings.Repeat("a/", 512) + "a"}, nc},
{".. and a: R3, in layer 4", []string{"..", "a"}, hi + ": file 1: R3"},
{"A.txt and a.txt", []string{"A.txt", "a.txt"}, hi + ": R7"},
{"A and a/b", []string{"A", "a/b"}, hi + ": R7"},
{"a/b and a/b/c", []string{"a/b", "a/b/c"}, hi + ": R7"},
{"Fotos/b and fotos/a", []string{"Fotos/b", "fotos/a"}, hi + ": R7"},
{"STRASSE and Straße", []string{"STRASSE", "Straße"}, hi + ": R7"},
{"k and the Kelvin sign", []string{"k", "\u212a"}, hi + ": R7"},
{"ab with and without ZWNJ", []string{"ab", "a\u200cb"}, hi + ": R7"},
{"the NFD and the NFC of a name", []string{"e\u0301", "\u00e9"}, hi + ": R7"},
}
}
// PathFoldVectors computes testdata/vectors/path_fold.json.
func PathFoldVectors() (PathFoldFile, error) {
f := PathFoldFile{
Spec: SpecVersion,
Description: "The key of R7 (spec §29.5) of segments, with the Unicode 18.0.0 tables of §29.5.1, generated by the reference implementation: " +
"nfd is NFD(segment) and key is NFD(fold(NFD(s'))), s' the segment without ZWNJ, ZWJ, VS15 and VS16. See testdata/README.md.",
UnicodeVersion: pathrule.UnicodeVersion,
TablesDigest: pathrule.TablesDigest,
}
var errs []error
for _, v := range foldCases() {
key := pathrule.Key(v.segment)
if v.key != "" && key != v.key {
errs = append(errs, fmt.Errorf("fold %q: key %+q, want %+q", v.name, key, v.key))
}
f.Keys = append(f.Keys, FoldVector{Name: v.name, Segment: v.segment, NFD: pathrule.NFD(v.segment), Key: key})
}
return f, errors.Join(errs...)
}
type foldCase struct{ name, segment, key string }
func foldCases() []foldCase {
return []foldCase{
{"ASCII capitals", "A.txt", "a.txt"},
{"sharp s, entry F", "Straße", "strasse"},
{"capital sharp s", "\u1e9e", "ss"},
{"the Kelvin sign, a singleton decomposition", "\u212a", "k"},
{"the Angstrom sign", "\u212b", "a\u030a"},
{"dotless i, the extra folding of NTFS", "\u0131", "i"},
{"capital I with a dot, entry F", "\u0130", "i\u0307"},
{"precomposed e acute", "\u00e9", "e\u0301"},
{"decomposed e acute", "e\u0301", "e\u0301"},
{"final sigma", "\u03c2", "\u03c3"},
{"U+0390, recursive decomposition", "\u0390", "\u03b9\u0308\u0301"},
{"a Cherokee small letter folds to the capital", "\uab70", "\u13a0"},
{"a Cherokee capital stays", "\u13a0", "\u13a0"},
{"ligature ff, entry F", "\ufb00", "ff"},
{"DZ with caron, titlecase", "\u01c5", "\u01c6"},
{"Hangul syllable with a final jamo", "\ud55c", "\u1112\u1161\u11ab"},
{"canonical order of two marks", "a\u0301\u0323", "a\u0323\u0301"},
{"ZWJ between marks is dropped before NFD", "a\u0301\u200d\u0323", "a\u0323\u0301"},
{"ZWNJ is dropped", "a\u200cb", "ab"},
{"VS16 and ZWJ of the rainbow flag are dropped", "\U0001f3f3\ufe0f\u200d\U0001f308", "\U0001f3f3\U0001f308"},
{"a reserved prefix, compared by its key", ".DateKeys-X", ".datekeys-x"},
{"full-width letters fold, not to ASCII", "\uff23\uff2f\uff2e", "\uff43\uff4f\uff4e"},
}
}
// HeadSchemaVectors computes testdata/vectors/head_schema.json, and fails if
// a vector does not get the result it is written for.
func HeadSchemaVectors() (HeadSchemaFile, error) {
f := HeadSchemaFile{
Spec: SpecVersion,
Description: "HEAD_CBOR of format 3 (spec §29.4 to §29.6) and the result of decoding it with no extension known, generated by the reference implementation: " +
"layer 2 (type tag and version), layer 3 (the CDDL with R1 and R8), then layer 4 in key order (spec §69.1). See testdata/README.md.",
}
var errs []error
for _, v := range headCases() {
b, err := cbortest.Marshal(v.value)
if err != nil {
return f, fmt.Errorf("head %q: %w", v.name, err)
}
got, detail := HeadResult(b)
full := got
if detail != "" {
full += ": " + detail
}
if err := check("head", v.name, full, v.want); err != nil {
errs = append(errs, err)
}
f.Heads = append(f.Heads, HeadVector{Name: v.name, Hex: hex.EncodeToString(b), Result: got, Detail: detail})
}
return f, errors.Join(errs...)
}
type headCase struct {
name string
value any
want string
}
func headCases() []headCase {
nc, un, hi := datekeys.ErrNonCanonicalCBOR.Code(), datekeys.ErrUnsupportedVersion.Code(), datekeys.ErrHeadInvalid.Code()
one := []any{entry3("a", 0, 0, 0)}
withFile := func(kv ...any) []any {
e := entry3("a", 0, 0, 0)
for i := 0; i < len(kv); i += 2 {
k := uint64(kv[i].(int))
if kv[i+1] == nil {
delete(e, k)
} else {
e[k] = kv[i+1]
}
}
return []any{e}
}
ext := func(id string) map[uint64]any { return map[uint64]any{0: id, 1: uint64(1)} }
return []headCase{
// Accepted.
{"no files and no comment", head3(), ResultOK},
{"a comment of one byte", head3(3, "a"), ResultOK},
{"a comment with TAB and LF", head3(3, "Hola\u0009mundo\u000aadiós"), ResultOK},
{"a comment with an emoji and VS16", head3(3, "\u2764\ufe0f para ti"), ResultOK},
{"a declared author", head3(4, "Ana López"), ResultOK},
{"one file of 0 bytes", head3(5, one), ResultOK},
{"a file with an mtime of 0", head3(5, withFile(5, uint64(0))), ResultOK},
{"a file with the last mtime, 9999-12-31T23:59:59Z", head3(5, withFile(5, uint64(capsule.MaxMTime))), ResultOK},
{"a comment of 16384 bytes", head3(3, strings.Repeat("a", 16384)), ResultOK},
{"a declared author of 256 bytes", head3(4, strings.Repeat("a", 256)), ResultOK},
{"a path of 1024 bytes, five segments of 204", head3(5, withFile(0, strings.Join([]string{strings.Repeat("a", 204), strings.Repeat("b", 204), strings.Repeat("c", 204), strings.Repeat("d", 204), strings.Repeat("e", 204)}, "/"))), ResultOK},
{"a noncritical extension", head3(7, []any{ext("org.example.a")}), ResultOK},
// Layer 2.
{"the type tag of CONTROL_CBOR", head3(0, capsule.ControlTypeTag), nc},
{"version 2", head3(1, uint64(2)), un},
{"version 0", head3(1, uint64(0)), un},
{"a byte string as the type tag", head3(0, []byte(capsule.HeadTypeTag)), nc},
// Layer 3.
{"no salt", head3(2, nil), nc},
{"a salt of 31 bytes", head3(2, make([]byte, 31)), nc},
{"a salt as a text string", head3(2, strings.Repeat("a", 32)), nc},
{"an empty comment", head3(3, ""), nc},
{"a comment of 16385 bytes", head3(3, strings.Repeat("a", 16385)), nc},
{"a comment as a byte string", head3(3, []byte("a")), nc},
{"a comment that is not UTF-8", head3(3, cbortest.Raw{0x62, 0x61, 0xff}), nc},
{"an empty declared author", head3(4, ""), nc},
{"a declared author of 257 bytes", head3(4, strings.Repeat("a", 257)), nc},
{"an empty array of files", head3(5, []any{}), nc},
{"a file without its SHA-256", head3(5, withFile(4, nil)), nc},
{"a file with a SHA-256 of 31 bytes", head3(5, withFile(4, make([]byte, 31))), nc},
{"a file with an unknown key 6", head3(5, withFile(6, uint64(0))), nc},
{"an empty path", head3(5, withFile(0, "")), nc},
{"a path of 1025 bytes", head3(5, withFile(0, strings.Repeat("a/", 512)+"a")), nc},
{"a path that is not UTF-8", head3(5, withFile(0, cbortest.Raw{0x62, 0x61, 0xff})), nc},
{"a size above L_MAX", head3(5, withFile(1, uint64(capsule.MaxPayloadLength+1), 3, uint64(capsule.MaxPayloadLength+1))), nc},
{"an mtime of 253402300800", head3(5, withFile(5, uint64(capsule.MaxMTime+1))), nc},
{"an unknown key 8", head3(8, uint64(0)), nc},
{"keys out of order", cbortest.Pairs{uint64(0), capsule.HeadTypeTag, uint64(1), uint64(1), uint64(3), "a", uint64(2), make([]byte, 32)}, nc},
{"a byte more after the map", cbortest.Raw(append(mustMarshal(head3()), 0x00)), nc},
{"paths b and a, in that order", head3(5, []any{entry3("b", 0, 0, 0), entry3("a", 0, 0, 0)}), nc},
{"paths b/.. and a: R8 before R3", head3(5, []any{entry3("b/..", 0, 0, 0), entry3("a", 0, 0, 0)}), nc},
{"an empty array of critical extensions", head3(6, []any{}), nc},
// Layer 4, in key order: the comment, the declared author, the files.
{"a comment with U+202E", head3(3, "a\u202eb"), hi + ": comment: text: bidirectional control U+202E"},
{"a comment with the tag U+E0041", head3(3, "Hola\U000e0041"), hi + ": comment: text: invisible U+E0041"},
{"a comment with tags that spell a text", head3(3, "Hola\U000e0049\U000e0047\U000e004e\U000e004f\U000e0052\U000e0041"), hi + ": comment: text: invisible U+E0049"},
{"a comment with VS16 after a letter", head3(3, "a\ufe0f"), hi + ": comment: text: line 1"},
{"a comment with a run of variation selectors", head3(3, "\u2764\ufe0f\ufe0f"), hi + ": comment: text: line 1"},
{"a comment with VS17", head3(3, "\U0001f600\U000e0100"), hi + ": comment: text: invisible U+E0100"},
{"a comment with CR LF", head3(3, "a\u000d\u000ab"), hi + ": comment: text: control U+000D"},
{"a comment with ZWJ at the end of a line", head3(3, "a\u200d\u000ab"), hi + ": comment: text: line 1"},
{"a declared author with LF", head3(4, "Ana\u000aLópez"), hi + ": declared author: text: control U+000A in the declared author"},
{"a declared author with TAB", head3(4, "Ana\u0009López"), hi + ": declared author: text: control U+0009 in the declared author"},
{"a declared author with a leading space", head3(4, " Ana"), hi + ": declared author: text: the declared author starts or ends with U+0020"},
{"a declared author with U+200E", head3(4, "a\u200eb"), hi + ": declared author: text: bidirectional control U+200E"},
{"path ..", head3(5, withFile(0, "..")), hi + ": file 1: R3: segment 1: the segment is two dots"},
{"path /a", head3(5, withFile(0, "/a")), hi + ": file 1: R2"},
{"path CON.txt", head3(5, withFile(0, "CON.txt")), hi + ": file 1: R6"},
{"path .datekeys-x", head3(5, withFile(0, ".datekeys-x")), hi + ": file 1: R10"},
{"a first start that is not 0", head3(5, []any{entry3("a", 1, 1, 2)}), hi + ": file 1: start 1 is not 0, the end of the file before"},
{"end minus start that is not size", head3(5, []any{entry3("a", 2, 0, 1)}), hi + ": file 1: from start 0 to end 1 is not the size 2"},
{"a gap between two files", head3(5, []any{entry3("a", 1, 0, 1), entry3("b", 1, 2, 3)}), hi + ": file 2: start 2 is not 1, the end of the file before"},
{"paths A.txt and a.txt", head3(5, []any{entry3("A.txt", 0, 0, 0), entry3("a.txt", 0, 0, 0)}), hi + ": R7: path 2 collides with path 1 in segment 1"},
{"a comment and a path that break: the comment first", head3(3, "a\u202e", 5, withFile(0, "..")), hi + ": comment"},
{"a path that breaks and an unknown critical extension: the path first", head3(5, withFile(0, ".."), 6, []any{ext("org.example.a")}), hi + ": file 1: R3"},
{"an unknown critical extension", head3(6, []any{ext("org.example.a")}), datekeys.ErrExtensionCriticalUnknown.Code()},
}
}
func mustMarshal(v any) []byte {
b, err := cbortest.Marshal(v)
if err != nil {
panic(err)
}
return b
}
// SecurityVectors computes testdata/vectors/security.json, and fails if a
// vector does not get the verdicts it is written for.
func SecurityVectors() (SecurityVectorFile, error) {
f := SecurityVectorFile{
Spec: SpecVersion,
Description: "SECURITY_CBOR of format 3, exactly its SECURITY_LEN bytes, and the verdicts of the signature and of the seal (spec §29.3, §29.7), " +
"generated by the reference implementation, which implements no alg and no seal_type. See testdata/README.md.",
}
var errs []error
for _, v := range securityCases() {
b, err := cbortest.Marshal(v.value)
if err != nil {
return f, fmt.Errorf("security %q: %w", v.name, err)
}
sig, seal := SecurityResult(b)
if sig != v.sig || seal != v.seal {
errs = append(errs, fmt.Errorf("security %q: %s and %s, want %s and %s", v.name, sig, seal, v.sig, v.seal))
}
f.Vectors = append(f.Vectors, SecurityVector{Name: v.name, Hex: hex.EncodeToString(b), Signature: sig, Seal: seal})
}
return f, errors.Join(errs...)
}
type securityCase struct {
name string
value any
sig, seal string
}
func securityCases() []securityCase {
outer := func(kv ...any) map[uint64]any {
m := map[uint64]any{0: capsule.SecurityTypeTag, 1: uint64(capsule.SecurityVersion)}
for i := 0; i < len(kv); i += 2 {
m[uint64(kv[i].(int))] = kv[i+1]
}
return m
}
signature := mustMarshal(map[uint64]any{0: uint64(1), 1: fill(0x11, 32), 2: append(fill(0x22, 32), fill(0x22, 32)...)})
seal := mustMarshal(map[uint64]any{0: uint64(1), 1: fill(0x33, 32)})
x, f0, f1, s0, s1, s2 := "X", "F0", "F1", "S0", "S1", "S2"
return []securityCase{
{"empty, as writers of this version write it", outer(), f0, s0},
{"a signature of alg 1", outer(2, signature), f1, s0},
{"a seal of seal_type 1", outer(3, seal), f0, s1},
{"a seal of seal_type 2, RFC 3161", outer(3, mustMarshal(map[uint64]any{0: uint64(2), 1: fill(0x33, 32)})), f0, s1},
{"a seal of seal_type 3, OpenTimestamps", outer(3, mustMarshal(map[uint64]any{0: uint64(3), 1: fill(0x33, 32)})), f0, s1},
{"a signature and a seal", outer(2, signature, 3, seal), f1, s1},
{"a signature of alg 0, and the seal intact", outer(2, mustMarshal(map[uint64]any{0: uint64(0), 1: fill(0x11, 32), 2: fill(0x22, 32)}), 3, seal), f1, s1},
{"a signature with an empty key, and the seal intact", outer(2, mustMarshal(map[uint64]any{0: uint64(1), 1: []byte{}, 2: fill(0x22, 32)}), 3, seal), f1, s1},
{"a signature that is not CBOR", outer(2, []byte{0xff}), f1, s0},
{"a signature with a byte more", outer(2, append(append([]byte{}, signature...), 0x00)), f1, s0},
{"a seal of seal_type 0", outer(3, mustMarshal(map[uint64]any{0: uint64(0), 1: fill(0x33, 32)})), f0, s2},
{"a seal that breaks its schema, with an unknown seal_type", outer(3, mustMarshal(map[uint64]any{0: uint64(99), 1: fill(0x33, 32), 2: uint64(0)})), f0, s2},
{"a seal that is not CBOR", outer(3, []byte{0xff}), f0, s2},
{"key 2 that is not a byte string", outer(2, map[uint64]any{0: uint64(1)}), x, x},
{"key 2 that is an empty byte string", outer(2, []byte{}), x, x},
{"an unknown key 4", outer(4, []byte{1}), x, x},
{"a byte more after the map", cbortest.Raw(append(mustMarshal(outer()), 0x00)), x, x},
{"version 2", outer(1, uint64(2)), x, x},
{"the type tag of the head", outer(0, capsule.HeadTypeTag), x, x},
{"keys 2 and 3 out of order", cbortest.Pairs{uint64(0), capsule.SecurityTypeTag, uint64(1), uint64(1), uint64(3), seal, uint64(2), signature}, x, x},
{"an array", []any{capsule.SecurityTypeTag, uint64(1)}, x, x},
}
}

@ -153,7 +153,37 @@ func vectors(dir string) error {
if err != nil {
return err
}
return testkit.WriteJSON(filepath.Join(dir, "tlock_ibe.json"), iv)
if err := testkit.WriteJSON(filepath.Join(dir, "tlock_ibe.json"), iv); err != nil {
return err
}
return format3Vectors(dir)
}
// format3Vectors writes the vectors of format 3 (spec §67): the paths, the
// keys of R7, the heads and security.
func format3Vectors(dir string) error {
paths, err := testkit.PathVectors()
if err != nil {
return err
}
fold, err := testkit.PathFoldVectors()
if err != nil {
return err
}
heads, err := testkit.HeadSchemaVectors()
if err != nil {
return err
}
security, err := testkit.SecurityVectors()
if err != nil {
return err
}
for name, v := range map[string]any{"paths.json": paths, "path_fold.json": fold, "head_schema.json": heads, "security.json": security} {
if err := testkit.WriteJSON(filepath.Join(dir, name), v); err != nil {
return err
}
}
return nil
}
// mutationsName is the -only name that rebuilds the capsules of the

@ -11,6 +11,7 @@ import (
"filippo.io/age"
"g.activething.com/go/DateKeys/internal/pathrule"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
)
@ -139,6 +140,79 @@ func TestSchemaVectors(t *testing.T) {
}
}
// The vectors of format 3 replay: each path, tree, key, head and security
// area of the committed files gets its recorded result from the
// implementation, with the tables the files name (spec §29.3 to §29.7).
func TestFormat3VectorFiles(t *testing.T) {
const dir = "../../testdata/vectors/"
var paths testkit.PathVectorFile
if err := testkit.ReadJSON(dir+"paths.json", &paths); err != nil {
t.Fatal(err)
}
var fold testkit.PathFoldFile
if err := testkit.ReadJSON(dir+"path_fold.json", &fold); err != nil {
t.Fatal(err)
}
for _, f := range []struct{ version, digest string }{{paths.UnicodeVersion, paths.TablesDigest}, {fold.UnicodeVersion, fold.TablesDigest}} {
if f.version != pathrule.UnicodeVersion || f.digest != pathrule.TablesDigest {
t.Fatalf("vectors of Unicode %s and tables %s", f.version, f.digest)
}
}
for _, v := range paths.Paths {
if got := testkit.PathResult(v.Path); got != v.Result {
t.Errorf("path %q: %q, want %q", v.Name, got, v.Result)
}
}
for _, v := range paths.Trees {
b, err := testkit.TreeHead(v.Paths)
if err != nil {
t.Fatal(err)
}
if got, detail := testkit.HeadResult(b); got != v.Result || detail != v.Detail {
t.Errorf("tree %q: %s %q, want %s %q", v.Name, got, detail, v.Result, v.Detail)
}
}
for _, v := range fold.Keys {
if pathrule.NFD(v.Segment) != v.NFD || pathrule.Key(v.Segment) != v.Key {
t.Errorf("key %q: NFD %+q, key %+q", v.Name, pathrule.NFD(v.Segment), pathrule.Key(v.Segment))
}
}
var heads testkit.HeadSchemaFile
if err := testkit.ReadJSON(dir+"head_schema.json", &heads); err != nil {
t.Fatal(err)
}
for _, v := range heads.Heads {
b, err := hex.DecodeString(v.Hex)
if err != nil {
t.Fatal(err)
}
if got, detail := testkit.HeadResult(b); got != v.Result || detail != v.Detail {
t.Errorf("head %q: %s %q, want %s %q", v.Name, got, detail, v.Result, v.Detail)
}
}
var security testkit.SecurityVectorFile
if err := testkit.ReadJSON(dir+"security.json", &security); err != nil {
t.Fatal(err)
}
verdicts := map[string]bool{}
for _, v := range security.Vectors {
b, err := hex.DecodeString(v.Hex)
if err != nil {
t.Fatal(err)
}
if sig, seal := testkit.SecurityResult(b); sig != v.Signature || seal != v.Seal {
t.Errorf("security %q: %s %s, want %s %s", v.Name, sig, seal, v.Signature, v.Seal)
}
verdicts[v.Signature], verdicts[v.Seal] = true, true
}
// Every verdict this version can reach has a vector.
for _, v := range []string{"X", "F0", "F1", "S0", "S1", "S2"} {
if !verdicts[v] {
t.Errorf("no vector gives %s", v)
}
}
}
// The committed vector files are what the generators compute now.
func TestVectorFilesAreCurrent(t *testing.T) {
cbor, err := testkit.CBORVectors()
@ -149,6 +223,22 @@ func TestVectorFilesAreCurrent(t *testing.T) {
if err != nil {
t.Fatal(err)
}
paths, err := testkit.PathVectors()
if err != nil {
t.Fatal(err)
}
fold, err := testkit.PathFoldVectors()
if err != nil {
t.Fatal(err)
}
heads, err := testkit.HeadSchemaVectors()
if err != nil {
t.Fatal(err)
}
security, err := testkit.SecurityVectors()
if err != nil {
t.Fatal(err)
}
for _, v := range []struct {
file string
want any
@ -156,6 +246,10 @@ func TestVectorFilesAreCurrent(t *testing.T) {
}{
{"cbor.json", cbor, &testkit.CBORVectorFile{}},
{"padding.json", padding, &testkit.PaddingVectorFile{}},
{"paths.json", paths, &testkit.PathVectorFile{}},
{"path_fold.json", fold, &testkit.PathFoldFile{}},
{"head_schema.json", heads, &testkit.HeadSchemaFile{}},
{"security.json", security, &testkit.SecurityVectorFile{}},
} {
if err := testkit.ReadJSON("../../testdata/vectors/"+v.file, v.got); err != nil {
t.Fatal(err)

@ -1247,6 +1247,86 @@
"hex": "a60070646174656b6579732d636f6e74726f6c010302582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222064800000000000000000702",
"result": "ERR_UNSUPPORTED_VERSION"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: minimal control, 103 bytes, L = 0",
"hex": "a60070646174656b6579732d636f6e74726f6c010302582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222064800000000000000000702",
"result": "ok"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: both extension arrays",
"hex": "a80070646174656b6579732d636f6e74726f6c0103025820111111111111111111111111111111111111111111111111111111111111111103582022222222222222222222222222222222222222222222222222222222222222220481a2006d6f72672e6578616d706c652e6101010581a3006d6f72672e6578616d706c652e620101024178064800000000000000000702",
"result": "ok"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: payload_length 84078 and padding 1",
"hex": "a60070646174656b6579732d636f6e74726f6c0103025820111111111111111111111111111111111111111111111111111111111111111103582022222222222222222222222222222222222222222222222222222222222222220648000000000001486e0701",
"result": "ok"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: payload_length L_MAX + 1",
"hex": "a60070646174656b6579732d636f6e74726f6c0103025820111111111111111111111111111111111111111111111111111111111111111103582022222222222222222222222222222222222222222222222222222222222222220648001fc000000000010702",
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: missing key 6, payload_length",
"hex": "a50070646174656b6579732d636f6e74726f6c0103025820111111111111111111111111111111111111111111111111111111111111111103582022222222222222222222222222222222222222222222222222222222222222220702",
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: missing key 7, padding",
"hex": "a50070646174656b6579732d636f6e74726f6c01030258201111111111111111111111111111111111111111111111111111111111111111035820222222222222222222222222222222222222222222222222222222222222222206480000000000000000",
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: schema version 3 without keys 6 and 7",
"hex": "a40070646174656b6579732d636f6e74726f6c010302582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222",
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: schema version 2, a valid format 2 control",
"hex": "a60070646174656b6579732d636f6e74726f6c010202582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222064800000000000000000702",
"result": "ERR_UNSUPPORTED_VERSION"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: schema version 1, a valid format 1 control",
"hex": "a40070646174656b6579732d636f6e74726f6c010102582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222",
"result": "ERR_UNSUPPORTED_VERSION"
},
{
"block": "control_cbor",
"schema": "control_cbor",
"format": 3,
"name": "format 3: schema version 4",
"hex": "a60070646174656b6579732d636f6e74726f6c010402582011111111111111111111111111111111111111111111111111111111111111110358202222222222222222222222222222222222222222222222222222222222222222064800000000000000000702",
"result": "ERR_UNSUPPORTED_VERSION"
},
{
"block": "dkk_body",
"schema": "dkk_body",

File diff suppressed because one or more lines are too long

@ -0,0 +1,140 @@
{
"spec": "0.10",
"description": "The key of R7 (spec §29.5) of segments, with the Unicode 18.0.0 tables of §29.5.1, generated by the reference implementation: nfd is NFD(segment) and key is NFD(fold(NFD(s'))), s' the segment without ZWNJ, ZWJ, VS15 and VS16. See testdata/README.md.",
"unicode_version": "18.0.0",
"tables_digest": "7bb770bac2c81497f520da6b079e6c4fcbcf140e937a2d4203da3ba7b46df2e2",
"keys": [
{
"name": "ASCII capitals",
"segment": "A.txt",
"nfd": "A.txt",
"key": "a.txt"
},
{
"name": "sharp s, entry F",
"segment": "Straße",
"nfd": "Straße",
"key": "strasse"
},
{
"name": "capital sharp s",
"segment": "ẞ",
"nfd": "ẞ",
"key": "ss"
},
{
"name": "the Kelvin sign, a singleton decomposition",
"segment": "K",
"nfd": "K",
"key": "k"
},
{
"name": "the Angstrom sign",
"segment": "Å",
"nfd": "Å",
"key": "å"
},
{
"name": "dotless i, the extra folding of NTFS",
"segment": "ı",
"nfd": "ı",
"key": "i"
},
{
"name": "capital I with a dot, entry F",
"segment": "İ",
"nfd": "İ",
"key": "i̇"
},
{
"name": "precomposed e acute",
"segment": "é",
"nfd": "é",
"key": "é"
},
{
"name": "decomposed e acute",
"segment": "é",
"nfd": "é",
"key": "é"
},
{
"name": "final sigma",
"segment": "ς",
"nfd": "ς",
"key": "σ"
},
{
"name": "U+0390, recursive decomposition",
"segment": "ΐ",
"nfd": "ΐ",
"key": "ΐ"
},
{
"name": "a Cherokee small letter folds to the capital",
"segment": "ꭰ",
"nfd": "ꭰ",
"key": "Ꭰ"
},
{
"name": "a Cherokee capital stays",
"segment": "Ꭰ",
"nfd": "Ꭰ",
"key": "Ꭰ"
},
{
"name": "ligature ff, entry F",
"segment": "ff",
"nfd": "ff",
"key": "ff"
},
{
"name": "DZ with caron, titlecase",
"segment": "Dž",
"nfd": "Dž",
"key": "dž"
},
{
"name": "Hangul syllable with a final jamo",
"segment": "한",
"nfd": "한",
"key": "한"
},
{
"name": "canonical order of two marks",
"segment": "ạ́",
"nfd": "ạ́",
"key": "ạ́"
},
{
"name": "ZWJ between marks is dropped before NFD",
"segment": "á‍̣",
"nfd": "á‍̣",
"key": "ạ́"
},
{
"name": "ZWNJ is dropped",
"segment": "a‌b",
"nfd": "a‌b",
"key": "ab"
},
{
"name": "VS16 and ZWJ of the rainbow flag are dropped",
"segment": "🏳️‍🌈",
"nfd": "🏳️‍🌈",
"key": "🏳🌈"
},
{
"name": "a reserved prefix, compared by its key",
"segment": ".DateKeys-X",
"nfd": ".DateKeys-X",
"key": ".datekeys-x"
},
{
"name": "full-width letters fold, not to ASCII",
"segment": "CON",
"nfd": "CON",
"key": "con"
}
]
}

@ -0,0 +1,562 @@
{
"spec": "0.10",
"description": "Paths of a format 3 head (spec §29.5) with the Unicode 18.0.0 and best-fit tables of §29.5.1, generated by the reference implementation. paths: one path and the rules of one entry, R2 to R6c and R10; trees: the paths of a head, of 0 bytes each, and the result of decoding it. See testdata/README.md.",
"unicode_version": "18.0.0",
"tables_digest": "7bb770bac2c81497f520da6b079e6c4fcbcf140e937a2d4203da3ba7b46df2e2",
"paths": [
{
"name": "a file",
"path": "nota.txt",
"result": "ok"
},
{
"name": "a file in two folders",
"path": "fotos/2025/playa.jpg",
"result": "ok"
},
{
"name": "non-ASCII letters",
"path": "música/canción.txt",
"result": "ok"
},
{
"name": "U+00BF, which bestfit1250 maps to '?'",
"path": "¿Qué es esto.jpg",
"result": "ok"
},
{
"name": "U+00A7, which bestfit874 maps to a C0 control",
"path": "§ 3 contrato.pdf",
"result": "ok"
},
{
"name": "U+2665, which bestfit874 maps to a C0 control",
"path": "Para ti ♥.jpg",
"result": "ok"
},
{
"name": "U+2192, which bestfit1253 maps to '\u003e'",
"path": "Madrid → Lisboa",
"result": "ok"
},
{
"name": "U+00A0 at the start of a segment: no table maps it to ASCII",
"path": " a",
"result": "ok"
},
{
"name": "U+00A0 at the end of a segment",
"path": "a ",
"result": "ok"
},
{
"name": "U+3000 at the start: bestfit1250 and others map it to U+0020",
"path": " a",
"result": "R6c: segment 1: code page 1250 maps the segment to one that breaks R5: the segment starts with U+0020"
},
{
"name": "U+3000 at the end",
"path": "a ",
"result": "R6c: segment 1: code page 1250 maps the segment to one that breaks R5: the segment ends with U+0020"
},
{
"name": "CON.txt in full-width forms",
"path": "CON.txt",
"result": "R6c: segment 1: code page 874 maps the segment to one that breaks R6: CON is a reserved device name"
},
{
"name": "U+2216 SET MINUS",
"path": "a∖b",
"result": "R6c: segment 1: code page 1250 maps the segment to one with U+005C"
},
{
"name": "U+2236 RATIO",
"path": "a∶b",
"result": "R6c: segment 1: code page 1250 maps the segment to one with U+003A"
},
{
"name": "U+00A5, which cp932 maps to '\\'",
"path": "a¥b",
"result": "R6c: segment 1: code page 932 maps the segment to one with U+005C"
},
{
"name": "U+20A9, which cp949 maps to '\\'",
"path": "a₩b",
"result": "R6c: segment 1: code page 949 maps the segment to one with U+005C"
},
{
"name": "U+00B4, which cp1253 maps to '/'",
"path": "a´b",
"result": "R6c: segment 1: code page 1253 maps the segment to one with U+002F"
},
{
"name": "full-width solidus",
"path": "a/b",
"result": "R6c: segment 1: code page 874 maps the segment to one with U+002F"
},
{
"name": "full-width reverse solidus",
"path": "a\b",
"result": "R6c: segment 1: code page 874 maps the segment to one with U+005C"
},
{
"name": "full-width colon",
"path": "a:b",
"result": "R6c: segment 1: code page 874 maps the segment to one with U+003A"
},
{
"name": "two full-width full stops",
"path": "..",
"result": "R6c: segment 1: code page 874 maps the segment to one that breaks R3: the segment is two dots"
},
{
"name": "8.3 alias",
"path": "ABCDEF~1",
"result": "R6b: segment 1: the segment has the form of an 8.3 alias"
},
{
"name": "8.3 alias with an extension",
"path": "ABCDEF~1.TXT",
"result": "R6b: segment 1: the segment has the form of an 8.3 alias"
},
{
"name": "~1 alone",
"path": "~1",
"result": "R6b: segment 1: the segment has the form of an 8.3 alias"
},
{
"name": "8.3 alias with a non-ASCII letter",
"path": "Ä~1.txt",
"result": "R6b: segment 1: the segment has the form of an 8.3 alias"
},
{
"name": "nine characters before ~1",
"path": "ABCDEFGHI~1",
"result": "ok"
},
{
"name": "~1 inside a name",
"path": "a~1b",
"result": "ok"
},
{
"name": "~2023 in a long name",
"path": "report~2023.txt",
"result": "ok"
},
{
"name": "unassigned U+0378",
"path": "a͸b",
"result": "R4: segment 1: unassigned U+0378"
},
{
"name": "noncharacter U+FFFE",
"path": "a￾b",
"result": "R4: segment 1: unassigned U+FFFE"
},
{
"name": "U+206A",
"path": "ab",
"result": "R4: segment 1: invisible U+206A"
},
{
"name": "U+206F",
"path": "ab",
"result": "R4: segment 1: invisible U+206F"
},
{
"name": "soft hyphen U+00AD",
"path": "a­b",
"result": "R4: segment 1: invisible U+00AD"
},
{
"name": "U+034F COMBINING GRAPHEME JOINER",
"path": "a͏b",
"result": "R4: segment 1: invisible U+034F"
},
{
"name": "U+200B ZERO WIDTH SPACE",
"path": "a​b",
"result": "R4: segment 1: invisible U+200B"
},
{
"name": "U+2060 WORD JOINER",
"path": "a⁠b",
"result": "R4: segment 1: invisible U+2060"
},
{
"name": "U+3164 HANGUL FILLER",
"path": "aㅤb",
"result": "R4: segment 1: invisible U+3164"
},
{
"name": "U+FEFF",
"path": "ab",
"result": "R4: segment 1: invisible U+FEFF"
},
{
"name": "U+202E RIGHT-TO-LEFT OVERRIDE",
"path": "a‮b",
"result": "R4: segment 1: invisible U+202E"
},
{
"name": "tag U+E0041",
"path": "a󠁁",
"result": "R4: segment 1: invisible U+E0041"
},
{
"name": "variation selector VS17",
"path": "😀󠄀",
"result": "R4: segment 1: invisible U+E0100"
},
{
"name": "the flag of Scotland, with tags",
"path": "🏴󠁧󠁢󠁳󠁣󠁴󠁿",
"result": "R4: segment 1: invisible U+E0067"
},
{
"name": "U+F03A of the private use area",
"path": "informeanexo",
"result": "R4: segment 1: private use U+F03A"
},
{
"name": "TAB",
"path": "a\tb",
"result": "R4: segment 1: control U+0009"
},
{
"name": "U+2028 LINE SEPARATOR",
"path": "a\u2028b",
"result": "R4: segment 1: separator U+2028"
},
{
"name": "':'",
"path": "a:b",
"result": "R4: segment 1: character U+003A"
},
{
"name": "'\\'",
"path": "a\\b",
"result": "R4: segment 1: character U+005C"
},
{
"name": "'?'",
"path": "a?b",
"result": "R4: segment 1: character U+003F"
},
{
"name": "'\"'",
"path": "a\"b",
"result": "R4: segment 1: character U+0022"
},
{
"name": "'*', '\u003c', '\u003e' and '|'",
"path": "a*b\u003cc\u003ed|e",
"result": "R4: segment 1: character U+002A"
},
{
"name": "a dot and ZWJ",
"path": ".‍",
"result": "R3: segment 1: the segment is a dot without ZWNJ, ZWJ, VS15 and VS16"
},
{
"name": "a segment of ZWJ alone",
"path": "‍",
"result": "R3: segment 1: the segment is empty without ZWNJ, ZWJ, VS15 and VS16"
},
{
"name": "two dots",
"path": "..",
"result": "R3: segment 1: the segment is two dots"
},
{
"name": "one dot inside",
"path": "a/./b",
"result": "R3: segment 2: the segment is a dot"
},
{
"name": "a segment of 256 bytes",
"path": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"result": "R3: segment 1: 256 bytes, more than 255"
},
{
"name": "a segment of 255 bytes",
"path": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"result": "ok"
},
{
"name": "127 times U+0390: 381 UTF-16 units after NFD",
"path": "ΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐ",
"result": "R3: segment 1: its NFD is 381 UTF-16 code units, more than 255"
},
{
"name": "85 times U+0390: 255 UTF-16 units after NFD",
"path": "ΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐΐ",
"result": "ok"
},
{
"name": "VS16 after U+2764, which admits it",
"path": "❤️.txt",
"result": "ok"
},
{
"name": "VS16 after a",
"path": "a️",
"result": "R4b: segment 1: U+FE0F is not part of an emoji variation sequence"
},
{
"name": "ZWJ at the start",
"path": "‍a",
"result": "R4b: segment 1: U+200D at the start"
},
{
"name": "ZWJ at the end",
"path": "a‍",
"result": "R4b: segment 1: U+200D at the end"
},
{
"name": "two ZWJ in a row",
"path": "a‍‍b",
"result": "R4b: segment 1: U+200D right after U+200D"
},
{
"name": "ZWNJ and ZWJ in a row",
"path": "a‌‍b",
"result": "R4b: segment 1: U+200D right after U+200C"
},
{
"name": "ZWNJ inside a name",
"path": "ab‌c",
"result": "ok"
},
{
"name": "the rainbow flag",
"path": "🏳️‍🌈",
"result": "ok"
},
{
"name": "a family, joined with ZWJ",
"path": "👨‍👩‍👧",
"result": "ok"
},
{
"name": "a leading slash",
"path": "/a",
"result": "R2: segment 1 is empty"
},
{
"name": "two slashes",
"path": "a//b",
"result": "R2: segment 2 is empty"
},
{
"name": "a trailing slash",
"path": "a/",
"result": "R2: segment 2 is empty"
},
{
"name": "33 segments",
"path": "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a",
"result": "R2: 33 segments, more than 32"
},
{
"name": "a leading space",
"path": " a",
"result": "R5: segment 1: the segment starts with U+0020"
},
{
"name": "a trailing space",
"path": "a ",
"result": "R5: segment 1: the segment ends with U+0020"
},
{
"name": "a trailing dot",
"path": "a.",
"result": "R5: segment 1: the segment ends with '.'"
},
{
"name": "CON.txt",
"path": "CON.txt",
"result": "R6: segment 1: CON is a reserved device name"
},
{
"name": "con",
"path": "con",
"result": "R6: segment 1: CON is a reserved device name"
},
{
"name": "Aux with a space before the dot",
"path": "Aux .log",
"result": "R6: segment 1: AUX is a reserved device name"
},
{
"name": "COM with a superscript one",
"path": "COM¹",
"result": "R6: segment 1: COM¹ is a reserved device name"
},
{
"name": "lpt9.doc",
"path": "lpt9.doc",
"result": "R6: segment 1: LPT9 is a reserved device name"
},
{
"name": "CONIN$",
"path": "CONIN$",
"result": "R6: segment 1: CONIN$ is a reserved device name"
},
{
"name": ".datekeys-x at the first level",
"path": ".datekeys-x",
"result": "R10: the first segment starts with \".datekeys-\""
},
{
"name": ".DateKeys-X at the first level, compared by its key",
"path": ".DateKeys-X/b",
"result": "R10: the first segment starts with \".datekeys-\""
},
{
"name": ".datekeys-x at another level",
"path": "a/.datekeys-x",
"result": "ok"
}
],
"trees": [
{
"name": "three files in two folders",
"paths": [
"a/b",
"a/c",
"d"
],
"result": "ok"
},
{
"name": "U+FF5E before U+1F600: UTF-8 byte order",
"paths": [
"~",
"😀"
],
"result": "ok"
},
{
"name": "U+1F600 before U+FF5E: UTF-16 order, not UTF-8 byte order",
"paths": [
"😀",
"~"
],
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"name": "b and a, in that order",
"paths": [
"b",
"a"
],
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"name": "b/.. and a: R8 comes first, in layer 3",
"paths": [
"b/..",
"a"
],
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"name": "the same path twice",
"paths": [
"a",
"a"
],
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"name": "a path of 1025 bytes",
"paths": [
"a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a"
],
"result": "ERR_NON_CANONICAL_CBOR"
},
{
"name": ".. and a: R3, in layer 4",
"paths": [
"..",
"a"
],
"result": "ERR_HEAD_INVALID",
"detail": "file 1: R3: segment 1: the segment is two dots"
},
{
"name": "A.txt and a.txt",
"paths": [
"A.txt",
"a.txt"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "A and a/b",
"paths": [
"A",
"a/b"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "a/b and a/b/c",
"paths": [
"a/b",
"a/b/c"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 makes a file of path 1 a folder, or the reverse, in segment 2"
},
{
"name": "Fotos/b and fotos/a",
"paths": [
"Fotos/b",
"fotos/a"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "STRASSE and Straße",
"paths": [
"STRASSE",
"Straße"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "k and the Kelvin sign",
"paths": [
"k",
"K"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "ab with and without ZWNJ",
"paths": [
"ab",
"a‌b"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
},
{
"name": "the NFD and the NFC of a name",
"paths": [
"é",
"é"
],
"result": "ERR_HEAD_INVALID",
"detail": "R7: path 2 collides with path 1 in segment 1"
}
]
}

@ -0,0 +1,132 @@
{
"spec": "0.10",
"description": "SECURITY_CBOR of format 3, exactly its SECURITY_LEN bytes, and the verdicts of the signature and of the seal (spec §29.3, §29.7), generated by the reference implementation, which implements no alg and no seal_type. See testdata/README.md.",
"vectors": [
{
"name": "empty, as writers of this version write it",
"hex": "a20071646174656b6579732d73656375726974790101",
"signature": "F0",
"seal": "S0"
},
{
"name": "a signature of alg 1",
"hex": "a30071646174656b6579732d73656375726974790101025869a30001015820111111111111111111111111111111111111111111111111111111111111111102584022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222",
"signature": "F1",
"seal": "S0"
},
{
"name": "a seal of seal_type 1",
"hex": "a30071646174656b6579732d73656375726974790101035826a200010158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F0",
"seal": "S1"
},
{
"name": "a seal of seal_type 2, RFC 3161",
"hex": "a30071646174656b6579732d73656375726974790101035826a200020158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F0",
"seal": "S1"
},
{
"name": "a seal of seal_type 3, OpenTimestamps",
"hex": "a30071646174656b6579732d73656375726974790101035826a200030158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F0",
"seal": "S1"
},
{
"name": "a signature and a seal",
"hex": "a40071646174656b6579732d73656375726974790101025869a30001015820111111111111111111111111111111111111111111111111111111111111111102584022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222035826a200010158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F1",
"seal": "S1"
},
{
"name": "a signature of alg 0, and the seal intact",
"hex": "a40071646174656b6579732d73656375726974790101025849a3000001582011111111111111111111111111111111111111111111111111111111111111110258202222222222222222222222222222222222222222222222222222222222222222035826a200010158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F1",
"seal": "S1"
},
{
"name": "a signature with an empty key, and the seal intact",
"hex": "a40071646174656b6579732d73656375726974790101025828a3000101400258202222222222222222222222222222222222222222222222222222222222222222035826a200010158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F1",
"seal": "S1"
},
{
"name": "a signature that is not CBOR",
"hex": "a30071646174656b6579732d736563757269747901010241ff",
"signature": "F1",
"seal": "S0"
},
{
"name": "a signature with a byte more",
"hex": "a30071646174656b6579732d7365637572697479010102586aa3000101582011111111111111111111111111111111111111111111111111111111111111110258402222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222200",
"signature": "F1",
"seal": "S0"
},
{
"name": "a seal of seal_type 0",
"hex": "a30071646174656b6579732d73656375726974790101035826a200000158203333333333333333333333333333333333333333333333333333333333333333",
"signature": "F0",
"seal": "S2"
},
{
"name": "a seal that breaks its schema, with an unknown seal_type",
"hex": "a30071646174656b6579732d73656375726974790101035829a300186301582033333333333333333333333333333333333333333333333333333333333333330200",
"signature": "F0",
"seal": "S2"
},
{
"name": "a seal that is not CBOR",
"hex": "a30071646174656b6579732d736563757269747901010341ff",
"signature": "F0",
"seal": "S2"
},
{
"name": "key 2 that is not a byte string",
"hex": "a30071646174656b6579732d7365637572697479010102a10001",
"signature": "X",
"seal": "X"
},
{
"name": "key 2 that is an empty byte string",
"hex": "a30071646174656b6579732d736563757269747901010240",
"signature": "X",
"seal": "X"
},
{
"name": "an unknown key 4",
"hex": "a30071646174656b6579732d73656375726974790101044101",
"signature": "X",
"seal": "X"
},
{
"name": "a byte more after the map",
"hex": "a20071646174656b6579732d7365637572697479010100",
"signature": "X",
"seal": "X"
},
{
"name": "version 2",
"hex": "a20071646174656b6579732d73656375726974790102",
"signature": "X",
"seal": "X"
},
{
"name": "the type tag of the head",
"hex": "a2006d646174656b6579732d686561640101",
"signature": "X",
"seal": "X"
},
{
"name": "keys 2 and 3 out of order",
"hex": "a40071646174656b6579732d73656375726974790101035826a200010158203333333333333333333333333333333333333333333333333333333333333333025869a30001015820111111111111111111111111111111111111111111111111111111111111111102584022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222",
"signature": "X",
"seal": "X"
},
{
"name": "an array",
"hex": "8271646174656b6579732d736563757269747901",
"signature": "X",
"seal": "X"
}
]
}
Loading…
Cancel
Save

Powered by TurnKey Linux.