157 lines
6.6 KiB
Go
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)
|
|
}
|