package frombus import ( "context" "crypto/ecdh" "crypto/rand" "encoding/base64" "encoding/json" "errors" "os" "reflect" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/overlay" ) // bus is a Reader over values held in memory, counting whose config values were read. type bus struct { placements map[string]delivery.Placement records map[string]delivery.NodeRecord modules map[string]delivery.Module manifests map[string][]byte values map[string]map[string]any // by node.module valuesRead []string own []compose.OwnSecretRow ownErr error } func (b *bus) Placements(context.Context) (map[string]delivery.Placement, error) { return b.placements, nil } func (b *bus) Records(context.Context) (map[string]delivery.NodeRecord, error) { return b.records, nil } func (b *bus) Modules(context.Context) (map[string]delivery.Module, error) { return b.modules, nil } func (b *bus) Manifest(_ context.Context, digest string) ([]byte, error) { raw, ok := b.manifests[digest] if !ok { return nil, errors.New("no such manifest") } return raw, nil } func (b *bus) OwnSecrets(context.Context, string) ([]compose.OwnSecretRow, error) { return b.own, b.ownErr } func (b *bus) Credentials(context.Context, string, []compose.CredentialKey, []compose.CredentialKey) ([]compose.Credential, []compose.Issued, error) { return nil, nil, nil } func (b *bus) Values(_ context.Context, node, module string) (map[string]any, error) { b.valuesRead = append(b.valuesRead, node+"."+module) return b.values[node+"."+module], nil } func publicKey(t *testing.T) string { t.Helper() k, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(k.PublicKey().Bytes()) } // Issue 1126 (S3, S4, S5): the bus's placement, node records, manifests and config values fill the same compose.Input the // controller's store fills for compose's own two-machine mesh (compose's aMesh: anchor the hub, laptop), and that Input // composes; a node reads only its own config values; what cannot be composed is refused, naming it. func TestInput(t *testing.T) { ctx := context.Background() xterm, err := os.ReadFile("../../catalogue/testdata/catalogue-835bf22b-xterm.module.json") if err != nil { t.Fatal(err) } digest := delivery.ManifestDigest(xterm) // A module in the settings form (plex and the media modules on one node, 2026-10-26): its config values are in its // layers, not mesh-config, so the placement carries them (MeshValues, Values) and mesh-config's are not read for it. media := []byte(`{"module":"media","version":"1","settings":{"lines":{"kind":"preference","default":0,"why":"kept"},` + `"name":{"kind":"preference","default":"x","why":"shown"}},"own-secrets":{"token":"/srv/media/token"},"resources":[{"id":"conf","type":"file",` + `"path":"/srv/media/conf","mode":"0644","content":"uid=${setting:lines} name=${setting:name}\n"}]}`) mediaDigest := delivery.ManifestDigest(media) keys := map[string]string{"anchor": publicKey(t), "laptop": publicKey(t)} caps := []string{"container-runtime", "systemd", "wireguard"} tunnel := &delivery.RecordTunnel{Interface: "wg0", PublicKey: keys["anchor"], Address: "10.77.0.1/24", Peers: []delivery.RecordTunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}, {PublicKey: keys["laptop"], Address: "10.77.0.2/32"}}} mk := func() *bus { return &bus{ placements: map[string]delivery.Placement{ "anchor": {Node: "anchor", Decided: delivery.NodeDecided{Endpoint: "anchor.example:51820", Site: "here", Hub: true, Address: "10.77.0.1", Tunnel: tunnel, PeerNames: map[string]string{"peer": "phone"}}, Modules: []delivery.PlacedModule{{Module: overlay.Name}}, Holds: []delivery.Holding{{Claim: "mesh-private-network", Scope: "mesh", Module: overlay.Name}}}, "laptop": {Node: "laptop", Decided: delivery.NodeDecided{Endpoint: "laptop.example:51820", Site: "here", Address: "10.77.0.2"}, Modules: []delivery.PlacedModule{{Module: overlay.Name}, {Module: "xterm", HasLayer: true, Ports: map[string]int{"80": 8080}}, {Module: "media", HasMesh: true, MeshValues: map[string]any{"name": "mesh"}, HasLayer: true, Layer: map[string]any{"places": map[string]any{"data": "/srv/data"}}, Values: map[string]any{"lines": float64(1000)}}}}, }, records: map[string]delivery.NodeRecord{ "anchor": {Node: "anchor", TunnelKey: keys["anchor"], Capabilities: caps, Contract: 2}, "laptop": {Node: "laptop", TunnelKey: keys["laptop"], Capabilities: caps, Authority: "the mesh's certificate"}, }, modules: map[string]delivery.Module{"xterm": {Manifest: digest}, "media": {Manifest: mediaDigest}, "unread": {}}, manifests: map[string][]byte{digest: xterm, mediaDigest: media}, values: map[string]map[string]any{"laptop.xterm": {"font": "mono"}, "anchor.xterm": {"font": "serif"}, "laptop.media": {"lines": "not mesh-config's"}}, own: []compose.OwnSecretRow{{Node: "laptop", Module: "media", Name: "token", Origin: "made", SealedTo: "sk", Sealed: "{}"}}, } } // What the controller's store gives for the same mesh, as compose's aMesh and SettingsFor build it. var private catalogue.Manifest raw, _ := json.Marshal(overlay.Manifest()) if err := json.Unmarshal(raw, &private); err != nil { t.Fatal(err) } parsed, err := catalogue.ParseManifestForInputs(xterm) if err != nil { t.Fatal(err) } parsedMedia, err := catalogue.ParseManifestForInputs(media) if err != nil { t.Fatal(err) } capsSet := map[string]bool{"container-runtime": true, "systemd": true, "wireguard": true} tun := compose.Tunnel{Interface: "wg0", PublicKey: keys["anchor"], Address: "10.77.0.1/24", Peers: []compose.TunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}, {PublicKey: keys["laptop"], Address: "10.77.0.2/32"}}} want := compose.Input{ Catalogue: map[string]catalogue.Manifest{overlay.Name: private, "xterm": parsed, "media": parsedMedia}, Nodes: []compose.Node{ {Name: "anchor", Place: compose.Place{Key: keys["anchor"], Endpoint: "anchor.example:51820", Site: "here", Hub: true, Address: "10.77.0.1"}, Assigned: []string{overlay.Name}, Capabilities: capsSet, Contract: 2, Stated: true}, {Name: "laptop", Place: compose.Place{Key: keys["laptop"], Endpoint: "laptop.example:51820", Site: "here", Address: "10.77.0.2"}, Assigned: []string{overlay.Name, "xterm", "media"}, Capabilities: capsSet, Ports: map[string]map[int]int{"xterm": {80: 8080}}, Settings: map[string][]catalogue.Layer{"xterm": {{From: "laptop", Values: map[string]any{"font": "mono"}}}, "media": {{From: catalogue.MeshWideLayer, Values: map[string]any{"name": "mesh"}}, {From: "laptop", Values: map[string]any{"lines": float64(1000)}, Placement: map[string]any{"places": map[string]any{"data": "/srv/data"}}}}}}, }, Holdings: []catalogue.Held{{Claim: "mesh-private-network", Scope: "mesh", Node: "anchor", Module: overlay.Name}}, OwnSecrets: []compose.OwnSecretRow{{Node: "laptop", Module: "media", Name: "token", Origin: "made", SealedTo: "sk", Sealed: "{}"}}, // The same value as a core credential's box, for a core module (ADR 0349 §16). KeptCoreBoxes: []compose.CoreBox{{Node: "laptop", Module: "media", Name: "token", SealedTo: "sk", Box: json.RawMessage("{}")}}, Tunnels: map[string]compose.FoundTunnel{"anchor": {Tunnel: tun}}, Adopted: &compose.AdoptedTunnel{Tunnel: tun, Hub: "anchor", Adopted: true}, // The controller's order is by address; laptop enrolled under its peer's key, and a person named the other. Authority: "the mesh's certificate", CarriedPeers: []compose.CarriedPeer{{PublicKey: keys["laptop"], Address: "10.77.0.2", EnrolledAs: "laptop"}, {PublicKey: "peer", Address: "10.77.0.9", Named: "phone"}}, } b := mk() got, err := Input(ctx, b, Mesh{}, Scope{Node: "laptop"}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, want) { gj, _ := json.MarshalIndent(got, "", " ") wj, _ := json.MarshalIndent(want, "", " ") t.Fatalf("Input from the bus differs from the store's:\n got %s\nwant %s", gj, wj) } if !reflect.DeepEqual(b.valuesRead, []string{"laptop." + overlay.Name, "laptop.xterm", "laptop.media"}) { t.Errorf("laptop read config values %v; a node reads its own slice alone", b.valuesRead) } // The same composer over both: the compositions from the bus are the store's, byte for byte. fps := map[string]string{overlay.Name: "src2:" + strings.Repeat("a", 64), "xterm": "src2:" + strings.Repeat("b", 64), "media": "src2:" + strings.Repeat("c", 64)} fromBus, _, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop"}, fps) if err != nil { t.Fatal(err) } fromStore, _, err := compose.Compositions(want, "laptop", fps, compose.Reading) if err != nil { t.Fatal(err) } if len(fromBus) == 0 || !reflect.DeepEqual(fromBus, fromStore) { t.Fatalf("compositions differ or are empty: %d from the bus, %d from the store", len(fromBus), len(fromStore)) } // ADR 0555 §6: Export is Input's inverse for what a person decides: the store's input exported, node by node, is the // placement the bus holds, and read back it is the store's input again. for node, placed := range mk().placements { got, err := Export(want, node) if err != nil { t.Fatal(err) } if gj, _ := delivery.EncodePlacement(got); !delivery.SamePlacement(got, placed) { pj, _ := delivery.EncodePlacement(placed) t.Errorf("%s exported as %s, want %s", node, gj, pj) } } if _, err := Export(want, "stranger"); err == nil { t.Error("a node the input lacks was exported") } b = mk() if every, err := Input(ctx, b, Mesh{}, Scope{Every: true}); err != nil || every.OwnSecrets != nil { t.Fatalf("composing every node: %v, own secrets %v (the vault answers a node about itself alone)", err, every.OwnSecrets) } if !reflect.DeepEqual(b.valuesRead, []string{"anchor." + overlay.Name, "laptop." + overlay.Name, "laptop.xterm", "laptop.media"}) { // Every node's placed modules, and nothing placed nowhere: anchor places no xterm. t.Errorf("the check read %v", b.valuesRead) } // A node none of whose modules declares an own secret composes without asking the vault (the review of L2: a node // with no secret composed nothing while the vault did not answer); one whose module does, refuses without it. quiet := mk() quiet.ownErr = errors.New("no responders") if _, err := Input(ctx, quiet, Mesh{}, Scope{Node: "anchor"}); err != nil { t.Errorf("anchor, with no own secret, needed the vault: %v", err) } if _, err := Input(ctx, quiet, Mesh{}, Scope{Node: "laptop"}); err == nil { t.Error("laptop's media, which declares an own secret, composed without the vault") } kept := mk() kept.manifests["old"] = xterm in, err := Input(ctx, kept, Mesh{}, Scope{Node: "laptop", Kept: map[string]string{"xterm": "old"}}) if err != nil || !reflect.DeepEqual(in.Nodes[1].Kept, map[string]catalogue.Manifest{"xterm": parsed}) || in.Nodes[0].Kept != nil { t.Errorf("the want's manifest is kept on the composing node alone: %v, %v", in.Nodes, err) } for name, c := range map[string]struct { change func(*bus) scope Scope says string }{ "no record": {func(b *bus) { delete(b.records, "laptop") }, Scope{Node: "anchor"}, "laptop, whose agent has written no record"}, "no placement": {func(b *bus) {}, Scope{Node: "stranger"}, "stranger has no placement"}, "a manifest unread": {func(b *bus) { delete(b.manifests, digest) }, Scope{Node: "laptop"}, "the manifest of xterm"}, "a kept one unread": {func(b *bus) {}, Scope{Node: "laptop", Kept: map[string]string{"xterm": "gone"}}, "digest gone"}, } { b := mk() c.change(b) if _, err := Input(ctx, b, Mesh{}, c.scope); err == nil || !strings.Contains(err.Error(), c.says) { t.Errorf("%s: %v, want it to say %q", name, err, c.says) } } }