From d2aeb2cc1feede2d29eb3da482a8bded7449aabe Mon Sep 17 00:00:00 2001 From: riccardom Date: Tue, 28 Jul 2026 12:30:49 +0200 Subject: [PATCH] Fixes second answer dropped (the one carrying the PQ KEM data) Prevents dropping concurrent answer / offer carrying the PQ ML-KEM data --- client/internal/peer/handshaker.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client/internal/peer/handshaker.go b/client/internal/peer/handshaker.go index 8301ea4ae..165d9db2d 100644 --- a/client/internal/peer/handshaker.go +++ b/client/internal/peer/handshaker.go @@ -91,14 +91,20 @@ type Handshaker struct { func NewHandshaker(log *log.Entry, config ConnConfig, signaler *Signaler, ice *WorkerICE, relay *WorkerRelay, metricsStages *MetricsStages) *Handshaker { h := &Handshaker{ - log: log, - config: config, - signaler: signaler, - ice: ice, - relay: relay, - metricsStages: metricsStages, - remoteOffersCh: make(chan OfferAnswer), - remoteAnswerCh: make(chan OfferAnswer), + log: log, + config: config, + signaler: signaler, + ice: ice, + relay: relay, + metricsStages: metricsStages, + // Buffered by 1: the single Listen goroutine can be busy handling an offer + // (sendAnswer does a blocking signal send) exactly when the matching answer + // arrives on the other channel. Unbuffered, that answer would hit the + // non-blocking send's default and be dropped — fatal for the post-quantum + // exchange, which needs the answer to converge. A 1-slot cushion lets it wait + // until Listen loops back, without ever blocking the signal receiver. + remoteOffersCh: make(chan OfferAnswer, 1), + remoteAnswerCh: make(chan OfferAnswer, 1), } // assume remote supports ICE until we learn otherwise from received offers h.remoteICESupported.Store(ice != nil)