99 lines
3.2 KiB
Go
99 lines
3.2 KiB
Go
// 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
|
|
}
|