From 74eb22c5e368214ad6a940e158d0c0ca3de68f19 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:45:22 +0000 Subject: [PATCH] remove debug logging in server/browser, run preview xftp server via cabal run to ensure the latest code is used --- src/Simplex/FileTransfer/Server.hs | 12 ++---- xftp-web/src/client.ts | 62 +++++++++++++-------------- xftp-web/src/protocol/handshake.ts | 12 +----- xftp-web/src/protocol/transmission.ts | 16 ++----- xftp-web/test/globalSetup.ts | 17 +++----- 5 files changed, 45 insertions(+), 74 deletions(-) diff --git a/src/Simplex/FileTransfer/Server.hs b/src/Simplex/FileTransfer/Server.hs index 7845af026..95fadfa2e 100644 --- a/src/Simplex/FileTransfer/Server.hs +++ b/src/Simplex/FileTransfer/Server.hs @@ -75,7 +75,7 @@ import Simplex.Messaging.Version import System.Environment (lookupEnv) import System.Exit (exitFailure) import System.FilePath (()) -import System.IO (hPrint, hPutStrLn, stderr, universalNewlineMode) +import System.IO (hPrint, hPutStrLn, universalNewlineMode) #ifdef slow_servers import System.Random (getStdRandom, randomR) #endif @@ -179,18 +179,12 @@ xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpira webHello = sniUsed && any (\(t, _) -> tokenKey t == "xftp-web-hello") (fst $ H.requestHeaders request) webHandshake = sniUsed && any (\(t, _) -> tokenKey t == "xftp-handshake") (fst $ H.requestHeaders request) processHello pk_ = do - liftIO $ hPutStrLn stderr $ "DEBUG processHello: bodyHead.len=" <> show (B.length bodyHead) <> " sniUsed=" <> show sniUsed <> " webHello=" <> show webHello - liftIO $ hPutStrLn stderr $ "DEBUG processHello: bodyHead first20=" <> show (B.take 20 bodyHead) challenge_ <- if | B.null bodyHead -> pure Nothing | sniUsed -> do - let unpadResult = C.unPad bodyHead - liftIO $ hPutStrLn stderr $ "DEBUG processHello: unPad result=" <> show (either show (\b -> "OK len=" <> show (B.length b) <> " bytes=" <> show (B.take 20 b)) unpadResult) - body <- liftHS unpadResult - let decodeResult = smpDecode body :: Either String XFTPClientHello - liftIO $ hPutStrLn stderr $ "DEBUG processHello: smpDecode result=" <> show (either id (\(XFTPClientHello wc) -> "OK challenge=" <> show (fmap B.length wc)) decodeResult) - XFTPClientHello {webChallenge} <- liftHS $ first show decodeResult + body <- liftHS $ C.unPad bodyHead + XFTPClientHello {webChallenge} <- liftHS $ first show (smpDecode body) pure webChallenge | otherwise -> throwE HANDSHAKE rng <- asks random diff --git a/xftp-web/src/client.ts b/xftp-web/src/client.ts index 358c2b6de..ecf0d2056 100644 --- a/xftp-web/src/client.ts +++ b/xftp-web/src/client.ts @@ -79,21 +79,18 @@ function createBrowserTransport(baseUrl: string): Transport { const effectiveUrl = typeof __XFTP_PROXY_PORT__ !== 'undefined' && __XFTP_PROXY_PORT__ ? '/xftp-proxy' : baseUrl - console.log('[DEBUG transport] baseUrl=%s effectiveUrl=%s proxy=%s', baseUrl, effectiveUrl, __XFTP_PROXY_PORT__) return { async post(body: Uint8Array, headers?: Record): Promise { - console.log('[DEBUG transport.post] url=%s bodyLen=%d headers=%o', effectiveUrl, body.length, headers) const resp = await fetch(effectiveUrl, { method: "POST", headers, body, }) - console.log('[DEBUG transport.post] status=%d statusText=%s', resp.status, resp.statusText) - console.log('[DEBUG transport.post] response headers:', Object.fromEntries(resp.headers.entries())) - if (!resp.ok) throw new Error(`fetch failed: ${resp.status}`) - const buf = new Uint8Array(await resp.arrayBuffer()) - console.log('[DEBUG transport.post] responseLen=%d first16=%s', buf.length, Array.from(buf.subarray(0, 16)).map(b => b.toString(16).padStart(2,'0')).join(' ')) - return buf + if (!resp.ok) { + console.error('[XFTP] fetch %s failed: %d %s', effectiveUrl, resp.status, resp.statusText) + throw new Error(`Server request failed: ${resp.status} ${resp.statusText}`) + } + return new Uint8Array(await resp.arrayBuffer()) }, close() {} } @@ -141,20 +138,17 @@ export async function connectXFTP(server: XFTPServer): Promise { try { // Step 1: send client hello with web challenge - console.log('[DEBUG connectXFTP] Step 1: sending client hello') const challenge = new Uint8Array(32) crypto.getRandomValues(challenge) const clientHelloBytes = encodeClientHello({webChallenge: challenge}) - console.log('[DEBUG connectXFTP] clientHelloBytes.length=%d', clientHelloBytes.length) const shsBody = await transport.post(clientHelloBytes, {"xftp-web-hello": "1"}) - console.log('[DEBUG connectXFTP] Step 1 done: shsBody.length=%d', shsBody.length) // Step 2: decode + verify server handshake - console.log('[DEBUG connectXFTP] Step 2: decoding server handshake') const hs = decodeServerHandshake(shsBody) - console.log('[DEBUG connectXFTP] Step 2 decoded: sessionId.length=%d certChain.length=%d signedKey.length=%d webProof=%s', - hs.sessionId.length, hs.certChainDer.length, hs.signedKeyDer.length, hs.webIdentityProof ? hs.webIdentityProof.length : 'null') - if (!hs.webIdentityProof) throw new Error("connectXFTP: no web identity proof") + if (!hs.webIdentityProof) { + console.error('[XFTP] Server did not provide web identity proof') + throw new Error("Server did not provide web identity proof") + } const idOk = verifyIdentityProof({ certChainDer: hs.certChainDer, signedKeyDer: hs.signedKeyDer, @@ -163,24 +157,29 @@ export async function connectXFTP(server: XFTPServer): Promise { sessionId: hs.sessionId, keyHash: server.keyHash }) - console.log('[DEBUG connectXFTP] Step 2 identity verified=%s', idOk) - if (!idOk) throw new Error("connectXFTP: identity verification failed") + if (!idOk) { + console.error('[XFTP] Server identity verification failed') + throw new Error("Server identity verification failed") + } // Step 3: version negotiation const vr = compatibleVRange(hs.xftpVersionRange, {minVersion: initialXFTPVersion, maxVersion: currentXFTPVersion}) - console.log('[DEBUG connectXFTP] Step 3: version range=%o negotiated=%o', hs.xftpVersionRange, vr) - if (!vr) throw new Error("connectXFTP: incompatible version") + if (!vr) { + console.error('[XFTP] Incompatible server version: %o', hs.xftpVersionRange) + throw new Error("Incompatible server version") + } const xftpVersion = vr.maxVersion // Step 4: send client handshake - console.log('[DEBUG connectXFTP] Step 4: sending client handshake v=%d', xftpVersion) const ack = await transport.post(encodeClientHandshake({xftpVersion, keyHash: server.keyHash}), {"xftp-handshake": "1"}) - console.log('[DEBUG connectXFTP] Step 4 done: ack.length=%d', ack.length) - if (ack.length !== 0) throw new Error("connectXFTP: non-empty handshake ack") + if (ack.length !== 0) { + console.error('[XFTP] Non-empty handshake ack (%d bytes)', ack.length) + throw new Error("Server handshake failed") + } - console.log('[DEBUG connectXFTP] handshake complete, sessionId=%s', Array.from(hs.sessionId.subarray(0, 8)).map(b => b.toString(16).padStart(2,'0')).join('')) return {baseUrl, sessionId: hs.sessionId, xftpVersion, transport} } catch (e) { + console.error('[XFTP] Connection to %s failed:', baseUrl, e) transport.close() throw e } @@ -195,23 +194,22 @@ async function sendXFTPCommand( cmdBytes: Uint8Array, chunkData?: Uint8Array ): Promise<{response: FileResponse, body: Uint8Array}> { - const cmdTag = String.fromCharCode(...cmdBytes.subarray(0, 4)) - console.log('[DEBUG sendXFTPCommand] cmd=%s entityId.len=%d chunkData=%s', cmdTag, entityId.length, chunkData ? chunkData.length : 'none') const corrId = new Uint8Array(0) const block = encodeAuthTransmission(client.sessionId, corrId, entityId, cmdBytes, privateKey) const reqBody = chunkData ? concatBytes(block, chunkData) : block - console.log('[DEBUG sendXFTPCommand] reqBody.length=%d (block=%d + chunk=%d)', reqBody.length, block.length, chunkData?.length ?? 0) const fullResp = await client.transport.post(reqBody) - console.log('[DEBUG sendXFTPCommand] fullResp.length=%d', fullResp.length) - if (fullResp.length < XFTP_BLOCK_SIZE) throw new Error("sendXFTPCommand: response too short (" + fullResp.length + " < " + XFTP_BLOCK_SIZE + ")") + if (fullResp.length < XFTP_BLOCK_SIZE) { + console.error('[XFTP] Response too short: %d bytes (expected >= %d)', fullResp.length, XFTP_BLOCK_SIZE) + throw new Error("Server response too short") + } const respBlock = fullResp.subarray(0, XFTP_BLOCK_SIZE) const body = fullResp.subarray(XFTP_BLOCK_SIZE) - console.log('[DEBUG sendXFTPCommand] respBlock first4: %s', Array.from(respBlock.subarray(0, 4)).map(b => b.toString(16).padStart(2,'0')).join(' ')) const {command} = decodeTransmission(client.sessionId, respBlock) - console.log('[DEBUG sendXFTPCommand] decoded command=%s', String.fromCharCode(...command.subarray(0, Math.min(20, command.length)))) const response = decodeResponse(command) - console.log('[DEBUG sendXFTPCommand] response.type=%s', response.type) - if (response.type === "FRErr") throw new Error("XFTP error: " + response.err.type) + if (response.type === "FRErr") { + console.error('[XFTP] Server error: %s', response.err.type) + throw new Error("Server error: " + response.err.type) + } return {response, body} } diff --git a/xftp-web/src/protocol/handshake.ts b/xftp-web/src/protocol/handshake.ts index b0ac19792..c00f8acef 100644 --- a/xftp-web/src/protocol/handshake.ts +++ b/xftp-web/src/protocol/handshake.ts @@ -85,34 +85,26 @@ export interface XFTPServerHandshake { // sigBytes = ByteString (1-byte len prefix, empty for Nothing) // Trailing bytes (Tail) are ignored for forward compatibility. export function decodeServerHandshake(block: Uint8Array): XFTPServerHandshake { - console.log('[DEBUG decodeServerHandshake] block.length=%d', block.length) const raw = blockUnpad(block) - console.log('[DEBUG decodeServerHandshake] unpadded.length=%d content=%s', raw.length, String.fromCharCode(...raw.subarray(0, Math.min(40, raw.length)))) // Detect error responses (server sends padded error string like "HANDSHAKE") if (raw.length < 20) { const text = String.fromCharCode(...raw) if (/^[A-Z_]+$/.test(text)) { - throw new Error("server handshake error: " + text) + console.error('[XFTP] Server handshake error: %s', text) + throw new Error("Server handshake error: " + text) } } const d = new Decoder(raw) const xftpVersionRange = decodeVersionRange(d) - console.log('[DEBUG decodeServerHandshake] versionRange=%o offset=%d', xftpVersionRange, d.offset()) const sessionId = decodeBytes(d) - console.log('[DEBUG decodeServerHandshake] sessionId.length=%d offset=%d', sessionId.length, d.offset()) // CertChainPubKey: smpEncode (encodeCertChain certChain, SignedObject signedPubKey) const certChainDer = decodeNonEmpty(decodeLarge, d) - console.log('[DEBUG decodeServerHandshake] certChain.length=%d offset=%d', certChainDer.length, d.offset()) const signedKeyDer = decodeLarge(d) - console.log('[DEBUG decodeServerHandshake] signedKey.length=%d offset=%d remaining=%d', signedKeyDer.length, d.offset(), d.remaining()) // webIdentityProof: 1-byte length-prefixed ByteString (empty = Nothing) let webIdentityProof: Uint8Array | null = null if (d.remaining() > 0) { const sigBytes = decodeBytes(d) - console.log('[DEBUG decodeServerHandshake] webProof.length=%d', sigBytes.length) webIdentityProof = sigBytes.length === 0 ? null : sigBytes - } else { - console.log('[DEBUG decodeServerHandshake] no webIdentityProof (remaining=0)') } // Remaining bytes are Tail (ignored for forward compatibility) return {xftpVersionRange, sessionId, certChainDer, signedKeyDer, webIdentityProof} diff --git a/xftp-web/src/protocol/transmission.ts b/xftp-web/src/protocol/transmission.ts index 6ec26c919..1bc6ffd84 100644 --- a/xftp-web/src/protocol/transmission.ts +++ b/xftp-web/src/protocol/transmission.ts @@ -92,32 +92,22 @@ export interface DecodedTransmission { // Call decodeResponse(command) from commands.ts to parse the response. // Matches xftpDecodeTClient with implySessId = False: reads and verifies sessionId from wire. export function decodeTransmission(sessionId: Uint8Array, block: Uint8Array): DecodedTransmission { - console.log('[DEBUG decodeTransmission] block.length=%d', block.length) const raw = blockUnpad(block) - console.log('[DEBUG decodeTransmission] unpadded.length=%d first8=%s', raw.length, Array.from(raw.subarray(0, 8)).map(b => b.toString(16).padStart(2,'0')).join(' ')) const d = new Decoder(raw) const count = d.anyByte() - console.log('[DEBUG decodeTransmission] batch count=%d', count) if (count !== 1) throw new Error("decodeTransmission: expected batch count 1, got " + count) const transmission = decodeLarge(d) - console.log('[DEBUG decodeTransmission] transmission.length=%d', transmission.length) const td = new Decoder(transmission) // Skip authenticator (server responses have empty auth) - const auth = decodeBytes(td) - console.log('[DEBUG decodeTransmission] auth.length=%d', auth.length) + decodeBytes(td) // implySessId = False: read sessionId from wire and verify const sessId = decodeBytes(td) - console.log('[DEBUG decodeTransmission] sessId.length=%d', sessId.length) if (sessId.length !== sessionId.length || !sessId.every((b, i) => b === sessionId[i])) { - console.log('[DEBUG decodeTransmission] SESSION MISMATCH expected=%s got=%s', - Array.from(sessionId.subarray(0, 8)).map(b => b.toString(16).padStart(2,'0')).join(''), - Array.from(sessId.subarray(0, 8)).map(b => b.toString(16).padStart(2,'0')).join('')) - throw new Error("decodeTransmission: session ID mismatch") + console.error('[XFTP] Session ID mismatch in server response') + throw new Error("Session ID mismatch in server response") } const corrId = decodeBytes(td) const entityId = decodeBytes(td) const command = td.takeAll() - console.log('[DEBUG decodeTransmission] corrId.len=%d entityId.len=%d command.len=%d cmd=%s', - corrId.length, entityId.length, command.length, String.fromCharCode(...command.subarray(0, Math.min(10, command.length)))) return {corrId, entityId, command} } diff --git a/xftp-web/test/globalSetup.ts b/xftp-web/test/globalSetup.ts index 6e1983988..2d2b3a6e3 100644 --- a/xftp-web/test/globalSetup.ts +++ b/xftp-web/test/globalSetup.ts @@ -1,4 +1,4 @@ -import {spawn, execSync, ChildProcess} from 'child_process' +import {spawn, ChildProcess} from 'child_process' import {createHash} from 'crypto' import {createConnection, createServer} from 'net' import {resolve, join, dirname} from 'path' @@ -34,11 +34,11 @@ let server: ChildProcess | null = null let isOwner = false async function setup() { - // Kill any stale server from a previous run + // Kill any stale server from a previous run (negative PID kills process group) if (existsSync(SERVER_PID_FILE)) { try { const serverPid = parseInt(readFileSync(SERVER_PID_FILE, 'utf-8').trim(), 10) - process.kill(serverPid, 'SIGTERM') + process.kill(-serverPid, 'SIGTERM') await new Promise(r => setTimeout(r, 500)) } catch (_) {} try { unlinkSync(LOCK_FILE) } catch (_) {} @@ -89,16 +89,13 @@ key: ${join(fixtures, 'web.key')} ` writeFileSync(join(cfgDir, 'file-server.ini'), iniContent) - // Resolve binary path once (avoids cabal rebuild check on every run) - const serverBin = execSync('cabal -v0 list-bin xftp-server', {encoding: 'utf-8'}).trim() - // Redirect server stderr to file so logs survive after setup exits const serverLogPath = join(tmpdir(), 'xftp-test-server.log') const stderrFd = openSync(serverLogPath, 'w') console.log('[runSetup] Server log:', serverLogPath) - // Spawn xftp-server as detached process so runSetup.ts can exit - server = spawn(serverBin, ['start'], { + // Spawn via cabal run to always use freshly built code + server = spawn('cabal', ['run', 'xftp-server', '--', 'start'], { env: { ...process.env, XFTP_SERVER_CFG_PATH: cfgDir, @@ -118,11 +115,11 @@ key: ${join(fixtures, 'web.key')} } export async function teardown() { - // Kill the xftp-server if it's running + // Kill the xftp-server process group if it's running if (existsSync(SERVER_PID_FILE)) { try { const serverPid = parseInt(readFileSync(SERVER_PID_FILE, 'utf-8').trim(), 10) - process.kill(serverPid, 'SIGTERM') + process.kill(-serverPid, 'SIGTERM') // Wait a bit for graceful shutdown await new Promise(r => setTimeout(r, 500)) } catch (_) {