diff --git a/internal/auth/auth.go b/internal/auth/auth.go index f4dcd11..c649807 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -20,6 +20,11 @@ const Anonymous TenantID = "" // A nil error with [Anonymous] means the connection is accepted in // non-multi-tenant mode. Any non-nil error must be treated as a rejection // and the caller must close conn. +// +// This interface is stream-oriented: it consumes a net.Conn and is intended +// for bondserver/tcpfwdserver wiring. UDP-mode auth (when needed) will get +// a separate interface taking a token []byte read out-of-band from the +// handshake, since udpserver has no per-tenant stream abstraction. type Authenticator interface { Authenticate(ctx context.Context, conn net.Conn) (TenantID, error) } diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 0000000..6cae62a --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,13 @@ +package auth + +import "testing" + +func TestNopAuthenticatorReturnsAnonymous(t *testing.T) { + tenant, err := NopAuthenticator{}.Authenticate(t.Context(), nil) + if err != nil { + t.Fatalf("NopAuthenticator.Authenticate: %v", err) + } + if tenant != Anonymous { + t.Fatalf("tenant = %q want Anonymous (%q)", tenant, Anonymous) + } +} diff --git a/internal/transport/dtlsdial/handshakesem_test.go b/internal/transport/dtlsdial/handshakesem_test.go new file mode 100644 index 0000000..3cf894e --- /dev/null +++ b/internal/transport/dtlsdial/handshakesem_test.go @@ -0,0 +1,39 @@ +package dtlsdial + +import ( + "context" + "errors" + "net" + "testing" + "time" +) + +// TestHandshakeSemBlocksWhenFull verifies the contract: when HandshakeSem +// is at capacity, a new Dial blocks on the slot acquisition and respects +// ctx cancellation. Validates the bound that prevents unbounded concurrent +// handshakes under high -n values. +func TestHandshakeSemBlocksWhenFull(t *testing.T) { + sem := make(chan struct{}, 1) + sem <- struct{}{} // pre-fill: cap = 1, full + + d := &Dialer{HandshakeSem: sem} + + pc, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + defer pc.Close() + peer := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1} + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + start := time.Now() + _, err = d.Dial(ctx, pc, peer) + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("err = %v want DeadlineExceeded", err) + } + if elapsed := time.Since(start); elapsed < 40*time.Millisecond { + t.Fatalf("Dial returned too fast (%v); expected to block on sem", elapsed) + } +} diff --git a/internal/transport/dtlsdial/selfsigned_test.go b/internal/transport/dtlsdial/selfsigned_test.go new file mode 100644 index 0000000..8e41e33 --- /dev/null +++ b/internal/transport/dtlsdial/selfsigned_test.go @@ -0,0 +1,16 @@ +package dtlsdial + +import "testing" + +func TestGenerateSelfSignedCert(t *testing.T) { + cert, err := GenerateSelfSignedCert() + if err != nil { + t.Fatalf("GenerateSelfSignedCert: %v", err) + } + if len(cert.Certificate) == 0 { + t.Fatal("certificate chain is empty") + } + if cert.PrivateKey == nil { + t.Fatal("private key is nil") + } +}