package link import ( "crypto/ed25519" "encoding/json" "strings" "testing" "time" ) // The subject and the signing context the controller holds the same words for (mesh-controller internal/link). func TestTheSetuidSearchAskKeepsItsSubjectAndContext(t *testing.T) { if AskSetuidSearchSubject("laptop") != "mesh.node.laptop.ask.setuid-search" { t.Fatalf("the subject is %q", AskSetuidSearchSubject("laptop")) } if SetuidSearchContext != "novox-mesh setuid-search v1\n" || SetuidSearchContext == HandOverContext { t.Fatalf("the signing context is %q", SetuidSearchContext) } } func signedSearch(t *testing.T, key ed25519.PrivateKey, context string, ask HandOverAsk) []byte { t.Helper() raw, _ := json.Marshal(ask) body, _ := json.Marshal(SignedHandOver{Ask: raw, Signature: ed25519.Sign(key, append([]byte(context), raw...))}) return body } // A search ask is believed as a hand-over is: signed by the mesh under its own context (a hand-over's signature // starts no search), for this node, unexpired, once, and naming no path. func TestASetuidSearchAskIsBelievedOnlySignedForThisNodeAndOnce(t *testing.T) { m, key := aMember(t) now := time.Date(2026, 10, 10, 2, 0, 0, 0, time.UTC) var asked []string handle := func(by string) (string, error) { asked = append(asked, by); return "a new search starts", nil } check := HandOverCheck{Signer: m.Signer, Node: "n1", Now: func() time.Time { return now }, Nonces: &Nonces{}} good := HandOverAsk{Node: "n1", By: " jo ", Expires: now.Add(HandOverGood), Nonce: aNonce} for name, body := range map[string][]byte{ "a hand-over's signature": signedSearch(t, key, HandOverContext, good), "another node": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n2", By: "jo", Expires: good.Expires, Nonce: aNonce}), "a path": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n1", Path: "/x", By: "jo", Expires: good.Expires, Nonce: aNonce}), "expired": signedSearch(t, key, SetuidSearchContext, HandOverAsk{Node: "n1", By: "jo", Expires: now, Nonce: aNonce}), } { if a := AnswerSetuidSearch(check, handle, body); !strings.HasSuffix(a.Refused, "no search was started") || a.Said != "" { t.Errorf("%s: %+v", name, a) } } if len(asked) != 0 { t.Fatalf("a refused ask reached the handler: %v", asked) } body := signedSearch(t, key, SetuidSearchContext, good) if a := AnswerSetuidSearch(check, handle, body); a.Said != "a new search starts" || len(asked) != 1 || asked[0] != "jo" { t.Fatalf("the good ask: %+v, %v", a, asked) } if a := AnswerSetuidSearch(check, handle, body); !strings.Contains(a.Refused, "taken already") { t.Fatalf("the same ask again: %+v", a) } if a := AnswerSetuidSearch(check, nil, body); a.Refused == "" { t.Fatalf("no handler: %+v", a) } }