package hostactors import ( "crypto/ed25519" "reflect" "slices" "strings" "testing" "time" ) func bindingRoster() Roster { key := ed25519.NewKeyFromSeed(make([]byte, ed25519.SeedSize)).Public().(ed25519.PublicKey) row := Box{EnrolledAt: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC), CensusPublicKey: key, Enrollment: strings.Repeat("a", 32), Namespace: "platform", BoxID: "box", Label: "label", Region: "ie", Pool: "default", Cloud: Cloud{Provider: "aws", Account: "111122223333", Region: "eu-west-1", InstanceID: "i-0abc"}, EvidenceSHA256: strings.Repeat("c", 64), State: "active", QuarantineRevision: 2, LossIDs: []string{"loss_1"}, Group: "group", Role: ""} sibling := row sibling.BoxID, sibling.Enrollment, sibling.Cloud.InstanceID = "sibling", strings.Repeat("b", 32), "i-0def" return Roster{ HomeRegion: "ie", RequestLimitNS: 30_000_000_000, Publication: "published", FloorSHA256: strings.Repeat("d", 64), EnrollmentAttempts: map[string]EnrollmentAttempt{}, Regions: map[string]Region{"ie": {Namespaces: []string{"platform"}, Pools: []string{"default"}}}, V: 1, DeploymentBucket: "deployment", Revision: 7, Boxes: map[string]Box{"box": row, "sibling": sibling}, Consoles: map[string]Console{}, Holds: map[string]Reservation{}, Activations: map[string]Reservation{}, } } // The v1 tuple is frozen: the golden digest pins its order and encoding, every // bound fact moves it, and every excluded fact leaves it alone. The field // lists below must name every Roster and Box field, so a field added later // fails here until someone decides which side it is on (and, if bound, writes // a v2 encoder). func TestBoxBindingTuple(t *testing.T) { // The encoded tuple and its sha256 (checked by hand with sha256sum). const tuple = `["carlos-box-binding-v1","deployment","ie",30000000000,"published","dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","platform","box","ie","default",["aws","111122223333","eu-west-1","i-0abc"],"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","active",2,"3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29"]` const golden = "878c8d6e101fac0431418841b065a199c0796a2b83d30ee86e64400e44f94dca" base := bindingRoster() if raw := string(boxBindingV1(base, base.Boxes["box"])); raw != tuple { t.Fatalf("v1 tuple encoding changed:\n got %s\nwant %s", raw, tuple) } got, ok := BoxBinding(base, "box") if !ok || !ValidBinding(got) { t.Fatalf("BoxBinding = %q, %v", got, ok) } if got != golden { t.Fatalf("golden binding changed: got %s want %s (the v1 tuple is frozen)", got, golden) } if _, ok := BoxBinding(base, "unknown"); ok { t.Fatal("unknown box returned a binding") } if _, ok := BoxBinding(Roster{}, "box"); ok { t.Fatal("empty roster returned a binding") } bound := map[string]func(*Roster, *Box){ "Roster.DeploymentBucket": func(r *Roster, _ *Box) { r.DeploymentBucket = "other" }, "Roster.HomeRegion": func(r *Roster, _ *Box) { r.HomeRegion = "uk" }, "Roster.RequestLimitNS": func(r *Roster, _ *Box) { r.RequestLimitNS++ }, "Roster.Publication": func(r *Roster, _ *Box) { r.Publication = "preparing" }, "Roster.FloorSHA256": func(r *Roster, _ *Box) { r.FloorSHA256 = strings.Repeat("e", 64) }, "Box.Enrollment": func(_ *Roster, b *Box) { b.Enrollment = strings.Repeat("f", 32) }, "Box.Namespace": func(_ *Roster, b *Box) { b.Namespace = "other" }, "Box.BoxID": func(_ *Roster, b *Box) { b.BoxID = "other" }, "Box.Region": func(_ *Roster, b *Box) { b.Region = "uk" }, "Box.Pool": func(_ *Roster, b *Box) { b.Pool = "other" }, "Box.Cloud.Provider": func(_ *Roster, b *Box) { b.Cloud.Provider = "other" }, "Box.Cloud.Account": func(_ *Roster, b *Box) { b.Cloud.Account = "444455556666" }, "Box.Cloud.Region": func(_ *Roster, b *Box) { b.Cloud.Region = "eu-west-2" }, "Box.Cloud.InstanceID": func(_ *Roster, b *Box) { b.Cloud.InstanceID = "i-0fff" }, "Box.EvidenceSHA256": func(_ *Roster, b *Box) { b.EvidenceSHA256 = strings.Repeat("9", 64) }, "Box.State": func(_ *Roster, b *Box) { b.State = "quarantined" }, "Box.QuarantineRevision": func(_ *Roster, b *Box) { b.QuarantineRevision++ }, "Box.CensusPublicKey": func(_ *Roster, b *Box) { b.CensusPublicKey = ed25519.NewKeyFromSeed(append(make([]byte, ed25519.SeedSize-1), 1)).Public().(ed25519.PublicKey) }, } excluded := map[string]func(*Roster, *Box){ "Roster.V": func(r *Roster, _ *Box) { r.V = 2 }, "Roster.Revision": func(r *Roster, _ *Box) { r.Revision++ }, "Roster.Holds": func(r *Roster, _ *Box) { r.Holds["hold_1"] = Reservation{ID: "hold_1", Host: "h", OpID: "op", Actors: map[string]string{"box": "x"}} }, "Roster.Activations": func(r *Roster, _ *Box) { r.Activations["act_1"] = Reservation{ID: "act_1", Actors: map[string]string{"box": "x"}} }, "Roster.EnrollmentAttempts": func(r *Roster, _ *Box) { r.EnrollmentAttempts["attempt"] = EnrollmentAttempt{AttemptID: "attempt", InstanceID: "i-0new"} }, "Roster.Consoles": func(r *Roster, _ *Box) { r.Consoles["i-console"] = Console{Enrollment: strings.Repeat("e", 32), InstanceID: "i-console", Namespaces: []string{"platform"}, DeploymentBucket: "deployment"} }, "Roster.Regions": func(r *Roster, _ *Box) { r.Regions["uk"] = Region{Namespaces: []string{"platform"}} }, "Roster.Boxes": func(r *Roster, _ *Box) { s := r.Boxes["sibling"] s.State, s.QuarantineRevision, s.LossIDs = "quarantined", 1, []string{"loss"} r.Boxes["sibling"] = s r.Boxes["third"] = Box{BoxID: "third"} }, "Box.Role": func(_ *Roster, b *Box) { b.Role = "platform" }, "Box.Label": func(_ *Roster, b *Box) { b.Label = "renamed" }, "Box.Group": func(_ *Roster, b *Box) { b.Group = "other-group" }, "Box.LossIDs": func(_ *Roster, b *Box) { b.LossIDs = append(b.LossIDs, "loss_2") }, "Box.EnrolledAt": func(_ *Roster, b *Box) { b.EnrolledAt = b.EnrolledAt.Add(time.Hour) }, } // Every field is classified, exactly once. var fields []string for _, ty := range []struct { prefix string t reflect.Type }{{"Roster.", reflect.TypeOf(Roster{})}, {"Box.", reflect.TypeOf(Box{})}, {"Box.Cloud.", reflect.TypeOf(Cloud{})}} { for i := 0; i < ty.t.NumField(); i++ { name := ty.prefix + ty.t.Field(i).Name if name == "Box.Cloud" { continue } fields = append(fields, name) } } for _, name := range fields { _, in := bound[name] _, out := excluded[name] if in == out { t.Errorf("%s: bound=%v excluded=%v; classify every field exactly once", name, in, out) } } if len(bound)+len(excluded) != len(fields) { t.Errorf("classified %d fields, struct has %d", len(bound)+len(excluded), len(fields)) } mutate := func(edit func(*Roster, *Box)) string { r := bindingRoster() b := r.Boxes["box"] edit(&r, &b) ref := "box" delete(r.Boxes, "box") r.Boxes[ref] = b out, ok := BoxBinding(r, ref) if !ok { t.Fatal("box vanished") } return out } names := func(m map[string]func(*Roster, *Box)) []string { var out []string for k := range m { out = append(out, k) } slices.Sort(out) return out } seen := map[string]string{got: "base"} for _, name := range names(bound) { d := mutate(bound[name]) if d == got { t.Errorf("%s changed but the binding did not", name) } if prior, dup := seen[d]; dup { t.Errorf("%s and %s gave the same binding", name, prior) } seen[d] = name } for _, name := range names(excluded) { if d := mutate(excluded[name]); d != got { t.Errorf("%s changed and the binding moved; it is excluded", name) } } } func TestValidBinding(t *testing.T) { for s, want := range map[string]bool{ strings.Repeat("a", 64): true, strings.Repeat("0", 64): true, strings.Repeat("A", 64): false, strings.Repeat("a", 63): false, strings.Repeat("a", 65): false, strings.Repeat("g", 64): false, "": false, "7": false, strings.Repeat("a", 63) + " ": false, } { if got := ValidBinding(s); got != want { t.Errorf("ValidBinding(%q) = %v, want %v", s, got, want) } } }