a659e5dc09
- pbsdr: on a 403 pre-check (non-default storage id, no ACL yet) self-grant via the root wrapper then re-read, instead of aborting before the grant — closes F4/R-22. Red-proof TestSelfGrant_PreCheck403DoesNotAbortBeforeGrant. - escrow preflight: late-bound CurrentPBSStorageID re-reads agent.json so a pbsdr-seeded pbs_storage_id flips the row green in-process (no restart). Red-proof TestEscrowPreflight_PBSStorageIDLiveReload. - internal/poke: contentless UDP poke listener bound exclusively to the box WG /32 (port 51822), leading-edge debounced, fires the hub-loop out-of-band trigger for an immediate desired-state cycle. First slice of R-13. Red-proofs TestBindConfinement + TestDebounceCoalescesBurst.
148 lines
4.8 KiB
Go
148 lines
4.8 KiB
Go
package poke
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net"
|
|
"net/netip"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func discard() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) }
|
|
|
|
// waitFor polls cond up to d, returning true if it became true.
|
|
func waitFor(d time.Duration, cond func() bool) bool {
|
|
deadline := time.Now().Add(d)
|
|
for time.Now().Before(deadline) {
|
|
if cond() {
|
|
return true
|
|
}
|
|
time.Sleep(2 * time.Millisecond)
|
|
}
|
|
return cond()
|
|
}
|
|
|
|
// TestBindConfinement is red-proof (a): the listener binds EXCLUSIVELY to the address the resolver
|
|
// returns (the box's WG /32), never the wildcard 0.0.0.0/::. A wildcard bind would ALSO accept the
|
|
// LAN address — the exact leak the confinement forbids. We assert the bound socket's local address
|
|
// IS the specific resolved address and is NOT unspecified, and that a datagram to it fires the
|
|
// trigger. RED-PROOF: a 0.0.0.0 bind makes BoundAddrPort().Addr().IsUnspecified() true → FAIL.
|
|
func TestBindConfinement(t *testing.T) {
|
|
want := netip.MustParseAddr("127.0.0.1") // stand-in for the WG /32 on a portable loopback
|
|
var fires atomic.Int32
|
|
l := NewListener(
|
|
func() (netip.Addr, bool) { return want, true },
|
|
func() { fires.Add(1) },
|
|
0, // OS-chosen port so the test never collides
|
|
discard(),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go l.Run(ctx)
|
|
|
|
bctx, bcancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer bcancel()
|
|
bound, ok := l.BoundAddrPort(bctx)
|
|
if !ok {
|
|
t.Fatal("listener never bound")
|
|
}
|
|
if bound.Addr() != want {
|
|
t.Fatalf("bound to %s, want the specific WG addr %s (a wildcard bind would leak to the LAN)", bound.Addr(), want)
|
|
}
|
|
if bound.Addr().IsUnspecified() {
|
|
t.Fatal("listener bound to the wildcard address (0.0.0.0/::) — NOT confined to the WG /32")
|
|
}
|
|
|
|
// Functional: a datagram to the WG address fires exactly one trigger.
|
|
conn, err := net.Dial("udp", bound.String())
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
if _, err := conn.Write([]byte("")); err != nil { // contentless
|
|
t.Fatalf("write poke: %v", err)
|
|
}
|
|
if !waitFor(2*time.Second, func() bool { return fires.Load() >= 1 }) {
|
|
t.Fatal("a poke to the bound WG address did not fire the trigger")
|
|
}
|
|
}
|
|
|
|
// TestDebounceCoalescesBurst is red-proof (b): a burst of M pokes within DebounceWindow yields ≤1
|
|
// trigger; after the window elapses a further poke fires again. onPoke uses an injected clock so
|
|
// the test is deterministic. RED-PROOF: remove the leading-edge guard (fire every time) → M fires
|
|
// for M pokes → the "want 1" assertion FAILS.
|
|
func TestDebounceCoalescesBurst(t *testing.T) {
|
|
var fires atomic.Int32
|
|
l := NewListener(nil, func() { fires.Add(1) }, 0, discard())
|
|
|
|
base := time.Unix(1_700_000_000, 0)
|
|
var clock atomic.Int64
|
|
clock.Store(base.UnixNano())
|
|
l.now = func() time.Time { return time.Unix(0, clock.Load()) }
|
|
|
|
// A burst of 10 pokes at the SAME instant → exactly one fire.
|
|
const burst = 10
|
|
for i := 0; i < burst; i++ {
|
|
l.onPoke()
|
|
}
|
|
if got := fires.Load(); got != 1 {
|
|
t.Fatalf("burst of %d pokes fired %d triggers, want exactly 1 (debounce coalescing)", burst, got)
|
|
}
|
|
|
|
// Still inside the window → no additional fire.
|
|
clock.Store(base.Add(l.window - time.Millisecond).UnixNano())
|
|
l.onPoke()
|
|
if got := fires.Load(); got != 1 {
|
|
t.Fatalf("a poke inside the debounce window fired again (total %d), want still 1", got)
|
|
}
|
|
|
|
// Window elapsed → the next poke fires (immediacy is not lost after the window).
|
|
clock.Store(base.Add(l.window + time.Millisecond).UnixNano())
|
|
l.onPoke()
|
|
if got := fires.Load(); got != 2 {
|
|
t.Fatalf("a poke after the window did not fire (total %d), want 2", got)
|
|
}
|
|
}
|
|
|
|
// TestRunWaitsForRegistration: with no WG address yet, Run keeps retrying (never returns early,
|
|
// never binds) — a lost/absent poke path is harmless; the 15-min cycle still reconciles.
|
|
func TestRunWaitsForRegistration(t *testing.T) {
|
|
var resolved atomic.Bool
|
|
l := NewListener(
|
|
func() (netip.Addr, bool) {
|
|
if resolved.Load() {
|
|
return netip.MustParseAddr("127.0.0.1"), true
|
|
}
|
|
return netip.Addr{}, false
|
|
},
|
|
func() {}, 0, discard(),
|
|
)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
done := make(chan struct{})
|
|
go func() { l.Run(ctx); close(done) }()
|
|
|
|
// Not bound while unregistered.
|
|
nb, nbcancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
|
defer nbcancel()
|
|
if _, ok := l.BoundAddrPort(nb); ok {
|
|
t.Fatal("listener bound before the box registered")
|
|
}
|
|
// Registration appears → it binds within a rebind cycle.
|
|
resolved.Store(true)
|
|
bctx, bcancel := context.WithTimeout(context.Background(), 3*rebindInterval)
|
|
defer bcancel()
|
|
if _, ok := l.BoundAddrPort(bctx); !ok {
|
|
t.Fatal("listener never bound after registration appeared")
|
|
}
|
|
cancel()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Run did not exit on ctx cancel")
|
|
}
|
|
}
|