Files
netbird/client/internal/pqkem/convergence_test.go
T
riccardom 56a8681b76 Collapse Driver and Manager in one.
- Have just one manager => one lock
 - Session state is needed in driver to => we have it available now.
 - Isomorphically align to rosenpass components and functionality

File	Role	                                  rosenpass equivalent
kem.go	primitive pure X25519MLKEM768	          crypto.go/handshake
message.go	Offer/Answer/Confirm + Encode/Decode  messages.go
manager.go	Manager stateful, single lock	      server logic
callbacks.go	WGCallbackHandler (seam output)	  Handler
Transport (interfaccia)	seam trasporto pluggable  Conn
2026-08-07 10:24:01 +02:00

89 lines
2.2 KiB
Go

package pqkem
import (
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type dropTransport struct{}
func (dropTransport) Send(string, []byte) error { return nil }
// gate is a loopback transport with a switchable drop flag.
type gate struct {
local string
peer *Manager
drop atomic.Bool
}
func (g *gate) Send(remote string, msg []byte) error {
if g.drop.Load() {
return nil
}
cp := append([]byte(nil), msg...)
return g.peer.HandleInbound(g.local, cp)
}
func TestManager_InitialTimeoutFailsImmediately(t *testing.T) {
wg := newFakeWG()
d := NewManager("bbbb", dropTransport{}, wg, time.Hour, nil) // bbbb > aaaa -> initiator
d.retryInterval = 5 * time.Millisecond
d.maxRetries = 3
d.AddPeer("aaaa")
defer d.Stop()
require.NoError(t, d.initiateRekey("aaaa"))
// no answer will ever come -> the initial exchange fails fast.
require.Eventually(t, func() bool {
wg.mu.Lock()
defer wg.mu.Unlock()
return len(wg.failed) == 1
}, time.Second, 5*time.Millisecond)
}
func TestManager_RekeyToleratesKFailures(t *testing.T) {
gA := &gate{local: "aaaa"}
gB := &gate{local: "bbbb"}
wgA := newFakeWG()
wgB := newFakeWG()
dA := NewManager("aaaa", gA, wgA, time.Hour, nil)
dB := NewManager("bbbb", gB, wgB, time.Hour, nil)
gA.peer = dB
gB.peer = dA
dB.retryInterval = 5 * time.Millisecond
dB.maxRetries = 2
dA.AddPeer("bbbb")
dB.AddPeer("aaaa")
defer dA.Stop()
defer dB.Stop()
// first exchange succeeds -> peer becomes established (subsequent failures are rekeys).
require.NoError(t, dB.initiateRekey("aaaa"))
require.NotEqual(t, PSK{}, wgB.psk("aaaa"))
// now drop B's outbound: rekeys can no longer converge.
gB.drop.Store(true)
// K-1 failures must NOT raise OnRekeyFailed.
for i := 0; i < DefaultMaxRekeyFailures-1; i++ {
require.NoError(t, dB.initiateRekey("aaaa"))
time.Sleep(50 * time.Millisecond)
}
require.Equal(t, 0, failedCount(wgB), "no failure before K attempts")
// the K-th failure raises it once.
require.NoError(t, dB.initiateRekey("aaaa"))
require.Eventually(t, func() bool { return failedCount(wgB) == 1 }, time.Second, 5*time.Millisecond)
}
func failedCount(f *fakeWG) int {
f.mu.Lock()
defer f.mu.Unlock()
return len(f.failed)
}