package testkit import ( "encoding/hex" "errors" "fmt" "strings" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/extension" ) // NoteVectorFile is testdata/vectors/note.json: the data of the public note // (spec v0.11, §24.1), the extension datekeys.note of PUBLIC_HEADER, and what // a writer and a reader do with it. A writer writes a note only when Result // is ResultOK, and otherwise refuses it with Detail; a reader shows it only // then, and otherwise treats it as unusable and says so. type NoteVectorFile struct { Spec string `json:"spec"` Description string `json:"description"` Notes []NoteVector `json:"notes"` } // NoteVector is the data of a note in hexadecimal, its result and, when it // fails, the text of the rule it breaks, without its prefix and its code. type NoteVector struct { Name string `json:"name"` Data string `json:"data"` Result string `json:"result"` Detail string `json:"detail,omitempty"` } // NoteResult checks the data of a note with the rules of §24.1, and returns // its result and the detail of a failure. func NoteResult(data []byte) (string, string) { err := extension.CheckNote(string(data)) if err == nil { return ResultOK, "" } if !errors.Is(err, datekeys.ErrExtensionDataInvalid) { return Result(err), err.Error() } return Result(err), strings.TrimSuffix(err.Error(), ": "+datekeys.ErrExtensionDataInvalid.Code()) } // NoteVectors computes testdata/vectors/note.json, and fails if a vector does // not get the result it is written for. func NoteVectors() (NoteVectorFile, error) { f := NoteVectorFile{ Spec: SpecVersion, Description: "The data of the public note, datekeys.note version 1 in the noncritical array of PUBLIC_HEADER (spec §24.1): " + "the text in UTF-8, from 1 to 1024 bytes, that meets the rules of the declared author of §29.6. See testdata/README.md.", } bad := datekeys.ErrExtensionDataInvalid.Code() var errs []error for _, c := range []struct { name string data []byte want string }{ {"a note", []byte("Cartas del viaje a Lisboa"), ResultOK}, {"letters that are not ASCII and an emoji", []byte("Ñandú, año 2026 \U0001F30D"), ResultOK}, {"1024 bytes", []byte(strings.Repeat("a", 1024)), ResultOK}, {"an emoji with VS16, which the whitelist admits", []byte("\u2764\ufe0f"), ResultOK}, {"no byte", []byte{}, bad}, {"1025 bytes", []byte(strings.Repeat("a", 1025)), bad}, {"a tab", []byte("a\tb"), bad}, {"a line feed", []byte("a\nb"), bad}, {"a space at the start", []byte(" a"), bad}, {"a space at the end", []byte("a "), bad}, {"a bidi override", []byte("a\u202eb"), bad}, {"a zero width space, ignorable", []byte("a\u200bb"), bad}, {"a byte order mark at the start", []byte("\ufeffHola"), bad}, {"a byte that is not UTF-8", []byte{'a', 0xff}, bad}, {"the UTF-8 of a lone surrogate", []byte{0xed, 0xa0, 0x80}, bad}, {"a noncharacter", []byte("a\ufffeb"), bad}, } { got, detail := NoteResult(c.data) if got != c.want { errs = append(errs, fmt.Errorf("note %q: %s, want %s", c.name, got, c.want)) } f.Notes = append(f.Notes, NoteVector{Name: c.name, Data: hex.EncodeToString(c.data), Result: got, Detail: detail}) } return f, errors.Join(errs...) }