123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
package nodeengineclient
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/keys"
|
|
)
|
|
|
|
// NodeWord is the last part of a node's own key in the states have and composed.
|
|
const NodeWord = "node"
|
|
|
|
var moduleNameOK = regexp.MustCompile(`^[-/_a-zA-Z0-9]+(\.[-/_a-zA-Z0-9]+)*$`)
|
|
|
|
// moduleKeyPart is a module's name as a key part (issue 1150): a name may hold a dot (`novox.be`), which a key part may not,
|
|
// so a dot is escaped as keys.EscapeName writes it (`novox=2ebe`). A name that is empty, or holds a wildcard, whitespace,
|
|
// `=`, or an empty part between dots, is refused.
|
|
func moduleKeyPart(module string) (string, error) {
|
|
if module == "" {
|
|
return "", fmt.Errorf("node-engine: the module name is empty")
|
|
}
|
|
if !moduleNameOK.MatchString(module) {
|
|
return "", fmt.Errorf("node-engine: the module name %q holds a wildcard, whitespace, '=', an empty part between dots or a character a key refuses", module)
|
|
}
|
|
return keys.EscapeName(module), nil
|
|
}
|
|
|
|
// parseModuleKeyPart is the inverse of moduleKeyPart; it refuses a part that is not the one moduleKeyPart writes for its name.
|
|
func parseModuleKeyPart(part string) (string, error) {
|
|
if err := keys.CheckName("module", part); err != nil {
|
|
return "", err
|
|
}
|
|
module, err := keys.UnescapeName(part)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if back, err := moduleKeyPart(module); err != nil || back != part {
|
|
return "", fmt.Errorf("node-engine: the module part %q is not a module name as a key writes it", part)
|
|
}
|
|
return module, nil
|
|
}
|
|
|
|
// HaveKey is the key, in the state have, of a module's have on a node: `<node>.<module>`. A module named `node` is refused,
|
|
// since `<node>.node` is the node's own facts.
|
|
func HaveKey(node, module string) (string, error) {
|
|
if err := keys.CheckName("node", node); err != nil {
|
|
return "", err
|
|
}
|
|
part, err := moduleKeyPart(module)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if module == NodeWord {
|
|
return "", fmt.Errorf(`node-engine: the module name "node" is refused: %s.node is the node's own key`, node)
|
|
}
|
|
return node + "." + part, nil
|
|
}
|
|
|
|
// ParseHaveKey is the inverse of HaveKey and of the node's own key: isNode is true for `<node>.node`, with module empty.
|
|
func ParseHaveKey(key string) (node, module string, isNode bool, err error) {
|
|
node, module, ok := strings.Cut(key, ".")
|
|
if !ok {
|
|
return "", "", false, fmt.Errorf("node-engine: %q is not a have key, <node>.<module>", key)
|
|
}
|
|
if err := keys.CheckName("node", node); err != nil {
|
|
return "", "", false, fmt.Errorf("node-engine: %q is not a have key: %w", key, err)
|
|
}
|
|
if module == NodeWord {
|
|
return node, "", true, nil
|
|
}
|
|
if module, err = parseModuleKeyPart(module); err != nil {
|
|
return "", "", false, fmt.Errorf("node-engine: %q is not a have key: %w", key, err)
|
|
}
|
|
return node, module, false, nil
|
|
}
|
|
|
|
// ComposedKey is the key, in the state composed, of a module's composition for a node at a fingerprint:
|
|
// `<node>.<module>.<hex>`, the hex being the fingerprint's key part (64 lowercase hex digits, the mesh-catalog client's
|
|
// KeyPart). A module named `node` is refused.
|
|
func ComposedKey(node, module, fingerprintPart string) (string, error) {
|
|
have, err := HaveKey(node, module)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !keys.IsHex64(fingerprintPart) {
|
|
return "", fmt.Errorf("node-engine: %q is not the key part of a fingerprint, 64 lowercase hex digits", fingerprintPart)
|
|
}
|
|
return have + "." + fingerprintPart, nil
|
|
}
|
|
|
|
// ParseComposedKey is the inverse of ComposedKey and of the node's own key `<node>.node` (isNode, with module and
|
|
// fingerprintPart empty). It refuses any other shape, naming the key.
|
|
func ParseComposedKey(key string) (node, module, fingerprintPart string, isNode bool, err error) {
|
|
refused := func(why error) (string, string, string, bool, error) {
|
|
return "", "", "", false, fmt.Errorf("node-engine: %q is not a composition key, <node>.<module>.<fingerprint hex> or <node>.node: %w", key, why)
|
|
}
|
|
parts := strings.Split(key, ".")
|
|
switch len(parts) {
|
|
case 2:
|
|
if parts[1] != NodeWord {
|
|
return refused(fmt.Errorf("a module's composition names its fingerprint"))
|
|
}
|
|
if err := keys.CheckName("node", parts[0]); err != nil {
|
|
return refused(err)
|
|
}
|
|
return parts[0], "", "", true, nil
|
|
case 3:
|
|
node, module, isNode, err := ParseHaveKey(parts[0] + "." + parts[1])
|
|
if err != nil {
|
|
return refused(err)
|
|
}
|
|
if isNode {
|
|
return refused(fmt.Errorf("the node's own key names no fingerprint"))
|
|
}
|
|
if !keys.IsHex64(parts[2]) {
|
|
return refused(fmt.Errorf("%q is not 64 lowercase hex digits", parts[2]))
|
|
}
|
|
return node, module, parts[2], false, nil
|
|
}
|
|
return refused(fmt.Errorf("the key has %d parts", len(parts)))
|
|
}
|