Files
mesh-delivery/modules/mesh-delivery-assigner/internal/assignment/digest.go
T

216 lines
8.2 KiB
Go

package assignment
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The digests here are of values a proposal's ask and a seed were bound to before this package: the fields are written in
// the order the first writer wrote them (the generated contract types order theirs by name), so a digest an earlier holder
// bound an ask to is the one this computes. Golden values in the tests hold it.
type providerForm struct {
Node string `json:"node"`
Module string `json:"module"`
}
type placedForm struct {
Module string `json:"module"`
Ports map[string]int64 `json:"ports,omitempty"`
Bindings map[string]providerForm `json:"bindings,omitempty"`
MeshWide map[string]any `json:"mesh-wide,omitempty"`
HasMesh bool `json:"has-mesh-wide,omitempty"`
Layer map[string]any `json:"layer,omitempty"`
HasLayer bool `json:"has-layer,omitempty"`
MeshValues map[string]any `json:"mesh-wide-values,omitempty"`
Values map[string]any `json:"values,omitempty"`
}
type holdingForm struct {
Claim string `json:"claim"`
Scope string `json:"scope,omitempty"`
Module string `json:"module"`
Site string `json:"site,omitempty"`
Kind string `json:"kind,omitempty"`
}
type decidedForm struct {
Endpoint string `json:"endpoint,omitempty"`
Site string `json:"site,omitempty"`
Hub bool `json:"hub,omitempty"`
Address string `json:"address,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"`
Tunnel *tunnelForm `json:"tunnel,omitempty"`
PeerNames map[string]string `json:"peer-names,omitempty"`
}
type tunnelForm struct {
Interface string `json:"interface"`
Unit string `json:"unit,omitempty"`
Config string `json:"config,omitempty"`
Port int64 `json:"port,omitempty"`
MTU int64 `json:"mtu,omitempty"`
Address string `json:"address,omitempty"`
Range string `json:"range,omitempty"`
PublicKey string `json:"public-key,omitempty"`
Peers []tunnelPeerForm `json:"peers,omitempty"`
}
type tunnelPeerForm struct {
PublicKey string `json:"public-key"`
Address string `json:"address,omitempty"`
}
type assignmentsForm struct {
V int `json:"v"`
Node string `json:"node"`
Revision string `json:"revision,omitempty"`
Decided decidedForm `json:"decided"`
Modules []placedForm `json:"modules"`
Pins map[string]providerForm `json:"pins,omitempty"`
Holds []holdingForm `json:"holds,omitempty"`
}
type joiningForm struct {
Identity string `json:"identity"`
Overlay string `json:"overlay,omitempty"`
Sealing string `json:"sealing"`
Serving string `json:"serving,omitempty"`
}
type changeForm struct {
Op string `json:"op"`
Node string `json:"node"`
Module string `json:"module,omitempty"`
Ports map[string]int64 `json:"ports,omitempty"`
Bindings map[string]providerForm `json:"bindings,omitempty"`
Provision string `json:"provision,omitempty"`
Provider *providerForm `json:"provider,omitempty"`
Claim string `json:"claim,omitempty"`
Scope string `json:"scope,omitempty"`
Kind string `json:"kind,omitempty"`
Site string `json:"site,omitempty"`
Field string `json:"field,omitempty"`
Value json.RawMessage `json:"value,omitempty"`
MeshWide map[string]any `json:"mesh-wide,omitempty"`
Layer map[string]any `json:"layer,omitempty"`
MeshValues map[string]any `json:"mesh-wide-values,omitempty"`
Values map[string]any `json:"values,omitempty"`
Whole *assignmentsForm `json:"whole,omitempty"`
Decided *decidedForm `json:"decided,omitempty"`
Keys *joiningForm `json:"keys,omitempty"`
}
func providerOf(p seat.Provider) providerForm { return providerForm{Node: p.Node, Module: p.Module} }
func providersOf(m map[string]seat.Provider) map[string]providerForm {
if len(m) == 0 {
return nil
}
out := make(map[string]providerForm, len(m))
for k, v := range m {
out[k] = providerOf(v)
}
return out
}
func tunnelOf(t *seat.FoundTunnel) *tunnelForm {
if t == nil {
return nil
}
out := &tunnelForm{Interface: t.Interface, Unit: t.Unit, Config: t.Config, Port: t.Port, MTU: t.Mtu, Address: t.Address, Range: t.Range, PublicKey: t.PublicKey}
for _, p := range t.Peers {
out.Peers = append(out.Peers, tunnelPeerForm{PublicKey: p.PublicKey, Address: p.Address})
}
return out
}
func decidedOf(d seat.NodeDecided) decidedForm {
return decidedForm{Endpoint: d.Endpoint, Site: d.Site, Hub: d.Hub, Address: d.Address, PublicDomain: d.PublicDomain,
Account: d.Account, AccountHome: d.AccountHome, AgentAccount: d.AgentAccount, AgentHome: d.AgentAccountHome,
Adopted: d.Adopted, Taken: d.Taken, Tunnel: tunnelOf(d.Tunnel), PeerNames: d.PeerNames}
}
func assignmentsOf(p seat.Assignments) assignmentsForm {
out := assignmentsForm{V: int(p.V), Node: p.Node, Revision: p.Revision, Decided: decidedOf(p.Decided),
Pins: providersOf(p.Pins)}
if p.Modules != nil {
out.Modules = []placedForm{}
}
for _, m := range p.Modules {
out.Modules = append(out.Modules, placedForm{Module: m.Module, Ports: m.Ports, Bindings: providersOf(m.Bindings),
MeshWide: m.MeshWide, HasMesh: m.HasMeshWide, Layer: m.Layer, HasLayer: m.HasLayer, MeshValues: m.MeshWideValues, Values: m.Values})
}
for _, h := range p.Holds {
out.Holds = append(out.Holds, holdingForm{Claim: h.Claim, Scope: h.Scope, Module: h.Module, Site: h.Site, Kind: h.Kind})
}
return out
}
func changeOf(c seat.ProposalChange) changeForm {
out := changeForm{Op: c.Op, Node: c.Node, Module: c.Module, Ports: c.Ports, Bindings: providersOf(c.Bindings), Provision: c.Provision,
Claim: c.Claim, Scope: c.Scope, Kind: c.Kind, Site: c.Site, Field: c.Field, MeshWide: c.MeshWide, Layer: c.Layer,
MeshValues: c.MeshWideValues, Values: c.Values}
if c.Provider != nil {
p := providerOf(*c.Provider)
out.Provider = &p
}
if c.Value != nil {
out.Value, _ = json.Marshal(c.Value)
}
if c.Whole != nil {
w := assignmentsOf(*c.Whole)
out.Whole = &w
}
if c.Decided != nil {
d := decidedOf(*c.Decided)
out.Decided = &d
}
if c.Keys != nil {
out.Keys = &joiningForm{Identity: c.Keys.Identity, Overlay: c.Keys.Overlay, Sealing: c.Keys.Sealing, Serving: c.Keys.Serving}
}
return out
}
// ExportDigest is the digest of an export's answer, every node's assignments, as the seed proposes it: the same
// assignments give the same digest, whatever order they came in.
func ExportDigest(assignments map[string]seat.Assignments) string {
h := sha256.New()
for _, node := range sortedKeys(assignments) {
p := assignments[node]
p.V = keys.Version // the export was made with the assignments encoded at the keys' version
raw, _ := json.Marshal(assignmentsOf(p))
fmt.Fprintf(h, "%s\n%s\n", node, raw)
}
return "sha256:" + hex.EncodeToString(h.Sum(nil))
}
// ChangesDigest is the digest of a proposal's changes, which its authorising ask is bound to (§5).
func ChangesDigest(p seat.Proposal) string {
var changes []changeForm
if p.Changes != nil {
changes = make([]changeForm, 0, len(p.Changes))
}
for _, c := range p.Changes {
changes = append(changes, changeOf(c))
}
raw, _ := json.Marshal(struct {
ID string `json:"id"`
Changes []changeForm `json:"changes"`
Base map[string]int64 `json:"base"`
}{p.ID, changes, p.Base})
sum := sha256.Sum256(raw)
return "sha256:" + hex.EncodeToString(sum[:])
}