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: `.`. A module named `node` is refused, // since `.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`, 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, .", 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: // `..`, 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` (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, .. or .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))) } // HaveNodeKey is the key, in the state have, of a node's own facts: `.node`. func HaveNodeKey(node string) (string, error) { if err := keys.CheckName("node", node); err != nil { return "", err } return node + "." + NodeWord, nil }