// Package keys holds what the mesh's kept values of more than one domain share: the version a kept value carries and // the escaping of a name into a key part (novox/hq ADR 0634 §1, §4; they were go/delivery's and are used by the // Delivery, Placement and Identity and access domains alike). It names no seat and no module. package keys import ( "encoding/hex" "encoding/json" "errors" "fmt" "regexp" "strings" ) // Version is the version of every kept value that carries one and the only one its readers read. const Version = 1 var nameOK = regexp.MustCompile(`^[-/_=a-zA-Z0-9]+$`) // ErrUnknownVersion is what CheckVersion returns for a value whose version is missing or not Version. Test it with // errors.Is. var ErrUnknownVersion = errors.New("keys: unknown value version") // CheckVersion refuses a value with no version or one this reader does not read. func CheckVersion(data []byte) error { var probe struct { V *int `json:"v"` } if err := json.Unmarshal(data, &probe); err != nil { return fmt.Errorf("keys: value is not JSON: %w", err) } if probe.V == nil { return fmt.Errorf("%w: no version in the value, this reader reads %d", ErrUnknownVersion, Version) } if *probe.V != Version { return fmt.Errorf("%w: the value is version %d, this reader reads %d", ErrUnknownVersion, *probe.V, Version) } return nil } // CheckName refuses a name that is empty or holds a dot, a wildcard, whitespace or a character a bus key refuses. func CheckName(kind, name string) error { if name == "" { return fmt.Errorf("keys: the %s name is empty", kind) } if !nameOK.MatchString(name) { return fmt.Errorf("keys: the %s name %q holds a dot, a wildcard, whitespace or a character a key refuses", kind, name) } return nil } var hex64 = regexp.MustCompile(`^[0-9a-f]{64}$`) // IsHex64 says whether s is 64 lowercase hex digits. func IsHex64(s string) bool { return hex64.MatchString(s) } // EscapeName is name as a key part: a character outside letters, digits, `-`, `/` and `_` is written `=` and its // two lowercase hex digits per byte, and `=` itself is escaped, so any non-empty name keys and a name of allowed // characters only keys as it is. UnescapeName is its inverse. func EscapeName(name string) string { var b strings.Builder for i := 0; i < len(name); i++ { c := name[i] switch { case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '-', c == '/', c == '_': b.WriteByte(c) default: fmt.Fprintf(&b, "=%02x", c) } } return b.String() } // UnescapeName is the inverse of EscapeName; it refuses a part with a bare `=` or any character EscapeName leaves // escaped. func UnescapeName(part string) (string, error) { var out []byte for i := 0; i < len(part); i++ { c := part[i] if c != '=' { if !nameOK.MatchString(string(c)) { return "", fmt.Errorf("keys: %q holds %q, which a key part escapes", part, c) } out = append(out, c) continue } if i+3 > len(part) { return "", fmt.Errorf("keys: %q ends in an incomplete escape", part) } v, err := hex.DecodeString(part[i+1 : i+3]) if err != nil || part[i+1:i+3] != strings.ToLower(part[i+1:i+3]) { return "", fmt.Errorf("keys: %q holds a malformed escape %q", part, part[i:i+3]) } out = append(out, v[0]) i += 2 } return string(out), nil }