Files
mesh-sdk/go/keys/keys.go
T

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
}