Files
mesh-sdk/go/delivery/noderecord.go
T

157 lines
6.6 KiB
Go

package delivery
import (
"encoding/json"
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// A node's record is what the node knows of itself, written by its own agent into the node-engine's state `records`,
// one key `record.<node>` per node, and read by every node and by the parts that compose (novox/hq ADR 0547 §2: "of
// other nodes it reads only what is public: their records"; issue 1126's prepare, L1 as narrowed by ADR 0549). Where a
// module runs, its host ports, pins, bindings and seat holdings are not in it: they are the catalogue's placement
// (placement.go). Nothing in it is secret: its keys are public halves, its accounts are login names.
//
// The agent writes it at start and whenever a fact it holds changes, only when the value differs, and says
// `node-record-changed`. Seeded from what the node runs and reports, never from the controller's store.
const (
// NodeRecordsState is the node-engine's state of node records, and NodeRecordsBucket its bucket.
NodeRecordsState = "records"
NodeRecordsBucket = "node-engine_" + NodeRecordsState
// NodeRecordsHistory is the revisions the records bucket keeps per key.
NodeRecordsHistory = 5
// NodeRecordChangedEvent is what a node's agent says when it wrote its record.
NodeRecordChangedEvent = "node-record-changed"
recordWord = "record"
)
// NodeRecordKey is the key, in NodeRecordsBucket, of one node's record: `record.<node>`.
func NodeRecordKey(node string) (string, error) {
if err := checkName("node", node); err != nil {
return "", err
}
return recordWord + "." + node, nil
}
// ParseNodeRecordKey is the inverse of NodeRecordKey; it refuses any other key, naming it.
func ParseNodeRecordKey(key string) (node string, err error) {
node, ok := strings.CutPrefix(key, recordWord+".")
if !ok || checkName("node", node) != nil {
return "", fmt.Errorf("delivery: %q is not a node record key, record.<node>", key)
}
return node, nil
}
// NodePlace is where the node is on the private network: its tunnel key (public), endpoint, site, whether it is the
// hub, and its address (compose.Place).
type NodePlace struct {
Key string `json:"key,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Site string `json:"site,omitempty"`
Hub bool `json:"hub,omitempty"`
Address string `json:"address,omitempty"`
}
// RecordTunnel is a tunnel found on the node, as compose.Tunnel holds it: public keys and addresses only.
type RecordTunnel struct {
Interface string `json:"interface"`
Unit string `json:"unit,omitempty"`
Config string `json:"config,omitempty"`
Port int `json:"port,omitempty"`
MTU int `json:"mtu,omitempty"`
Address string `json:"address,omitempty"`
Range string `json:"range,omitempty"`
PublicKey string `json:"public-key,omitempty"`
Peers []RecordTunnelPeer `json:"peers,omitempty"`
}
// RecordTunnelPeer is one peer of a found tunnel.
type RecordTunnelPeer struct {
PublicKey string `json:"public-key"`
Address string `json:"address,omitempty"`
}
// RecordCarriedPeer is a peer of the tunnel the hub adopted, which the hub carries (compose.CarriedPeer).
type RecordCarriedPeer struct {
PublicKey string `json:"public-key"`
Address string `json:"address,omitempty"`
EnrolledAs string `json:"enrolled-as,omitempty"`
Named string `json:"named,omitempty"`
}
// NodeRecord is one node's record. Tunnel is the tunnel found on it whose key is its private-network key; on the hub
// alone, Adopted says whether the hub took its found tunnel over and Carried is every peer of it the hub carries, so
// the tunnels and the carried peers are each one node's own and need no other home.
type NodeRecord struct {
V int `json:"v"`
Node string `json:"node"`
Place NodePlace `json:"place"`
Capabilities []string `json:"capabilities,omitempty"`
PublicDomain string `json:"public-domain,omitempty"`
Account string `json:"account,omitempty"`
AccountHome string `json:"account-home,omitempty"`
AgentAccount string `json:"agent-account,omitempty"`
AgentHome string `json:"agent-account-home,omitempty"`
Adopted bool `json:"adopted,omitempty"`
Taken []string `json:"taken,omitempty"`
SealingKey string `json:"sealing-key,omitempty"`
ServingKey string `json:"serving-key,omitempty"`
Certificate string `json:"certificate,omitempty"`
OutwardLinks []string `json:"outward-links,omitempty"`
Networks []catalogue.AttachedNetwork `json:"networks,omitempty"`
NetworksSaid bool `json:"networks-said,omitempty"`
Contract int `json:"contract,omitempty"`
Tunnel *RecordTunnel `json:"tunnel,omitempty"`
HubAdopted bool `json:"hub-adopted,omitempty"`
Carried []RecordCarriedPeer `json:"carried,omitempty"`
}
func (r NodeRecord) check() error {
if err := checkName("node", r.Node); err != nil {
return err
}
if !r.Place.Hub && (r.HubAdopted || len(r.Carried) > 0) {
return fmt.Errorf("delivery: the record of %s carries peers or an adopted tunnel, which only the hub's does", r.Node)
}
if r.HubAdopted && r.Tunnel == nil {
return fmt.Errorf("delivery: the record of %s says the hub adopted its tunnel, and names none", r.Node)
}
for _, c := range r.Capabilities {
if c == "" {
return fmt.Errorf("delivery: the record of %s names an empty capability", r.Node)
}
}
return nil
}
// EncodeNodeRecord is r as JSON at Version; it refuses what DecodeNodeRecord refuses.
func EncodeNodeRecord(r NodeRecord) ([]byte, error) {
if err := r.check(); err != nil {
return nil, err
}
r.V = Version
return json.Marshal(r)
}
// DecodeNodeRecord reads a node record; it refuses an unknown version (ErrUnknownVersion), a node that is not a name,
// carried peers or an adopted tunnel on a node that is not the hub, and an empty capability.
func DecodeNodeRecord(data []byte) (NodeRecord, error) {
var r NodeRecord
if err := decode(data, &r); err != nil {
return NodeRecord{}, err
}
if err := r.check(); err != nil {
return NodeRecord{}, err
}
return r, nil
}
// SameNodeRecord says whether two records are the same value, so the agent writes only when they differ.
func SameNodeRecord(a, b NodeRecord) bool {
a.V, b.V = 0, 0
return jsonEqual(a, b)
}