From 4c1bd7e31f31ae62dba99997b3e1684abe1188f1 Mon Sep 17 00:00:00 2001 From: shum Date: Thu, 28 May 2026 13:06:19 +0000 Subject: [PATCH] agent: don't wait for SMPServerHandshakeResponse when clientService not sent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When negotiated SMP version is in [16, 18] (serviceCertsSMPVersion .. rcvServiceSMPVersion - 1) and the client has rcv-services enabled with SRMessaging role, mkClientService correctly drops the clientService portion, but the wait for SMPServerHandshakeResponse was gated on serviceKeys instead of clientService — so the client hung forever on a response the server (correctly) never sent. Gate the wait on the on-wire value clientService instead. Reproduced against smp15 (v6.5.0.14, max v18). --- src/Simplex/Messaging/Transport.hs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Messaging/Transport.hs b/src/Simplex/Messaging/Transport.hs index f1eb1a8bd0..4970d37f11 100644 --- a/src/Simplex/Messaging/Transport.hs +++ b/src/Simplex/Messaging/Transport.hs @@ -141,7 +141,9 @@ import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, parseRead1, sumTypeJSON) import Simplex.Messaging.Transport.Buffer import Simplex.Messaging.Transport.Shared -import Simplex.Messaging.Util (bshow, catchAll, catchAll_, liftEitherWith) +import Simplex.Messaging.Util (bshow, catchAll, catchAll_, liftEitherWith, tshow) +import Control.Logger.Simple (logInfo) +import Data.Maybe (isJust) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal import System.IO.Error (isEOFError) @@ -795,7 +797,9 @@ smpServerHandshake srvCert srvSignKey c (k, pk) kh smpVRange getService = do -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a smpClientHandshake :: forall c. Transport c => c 'TClient -> Maybe C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> Bool -> Maybe (ServiceCredentials, C.KeyPairEd25519) -> ExceptT TransportError IO (THandleSMP c 'TClient) smpClientHandshake c ks_ keyHash@(C.KeyHash kh) vRange proxyServer serviceKeys_ = do + liftIO . logInfo $ "smpClientHandshake: awaiting server hello" SMPServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th + liftIO . logInfo $ "smpClientHandshake: server hello received, serverVersionRange=" <> tshow smpVersionRange when (sessionId /= sessId) $ throwE TEBadSession -- Below logic downgrades version range in case the "client" is SMP proxy server and it is -- connected to the destination server of the version 11 or older. @@ -829,8 +833,20 @@ smpClientHandshake c ks_ keyHash@(C.KeyHash kh) vRange proxyServer serviceKeys_ _ -> Nothing clientService = mkClientService v =<< serviceKeys hs = SMPClientHandshake {smpVersion = v, keyHash, authPubKey = fst <$> ks_, proxyServer, clientService} + liftIO . logInfo $ "smpClientHandshake: sending client hello, negotiatedVersion=" <> tshow v <> ", clientService=" <> tshow (isJust clientService) sendHandshake th hs - service <- mapM getClientService serviceKeys + -- Only wait for SMPServerHandshakeResponse if we actually sent the + -- clientService portion in our hello. `serviceKeys` may be Just while + -- `clientService` is Nothing when mkClientService gates the role + -- against rcvServiceSMPVersion (≥19) while serviceCertsSMPVersion is + -- 16 — that window (v ∈ [16,18]) caused the smp15 hang. + service <- case clientService of + Just _ -> forM serviceKeys $ \sks -> do + liftIO . logInfo $ "smpClientHandshake: awaiting service handshake response" + r <- getClientService sks + liftIO . logInfo $ "smpClientHandshake: service handshake complete" + pure r + Nothing -> pure Nothing liftIO $ smpTHandleClient th v vr (snd <$> ks_) ck_ proxyServer service Nothing -> throwE TEVersion where