From 9478cdf049963de2ec3f7ca55b60ce58492f6770 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Tue, 9 Jun 2026 13:13:35 -0400 Subject: [PATCH] Reset SAS for device transfer on reconnect and hard abort if disconnected during transfer. --- .../DeviceTransferSetupViewModel.java | 4 +++- .../devicetransfer/NetworkClientThread.java | 22 ++++++++++++++++--- .../devicetransfer/NetworkServerThread.java | 21 +++++++++++++++--- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/DeviceTransferSetupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/DeviceTransferSetupViewModel.java index 1f01667756..23e054d1f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/DeviceTransferSetupViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/devicetransfer/DeviceTransferSetupViewModel.java @@ -24,7 +24,9 @@ public final class DeviceTransferSetupViewModel extends ViewModel { public DeviceTransferSetupViewModel() { this.store = new Store<>(new DeviceSetupState()); - this.distinctStepChanges = LiveDataUtil.distinctUntilChanged(this.store.getStateLiveData(), (current, next) -> current.getCurrentSetupStep() == next.getCurrentSetupStep()); + this.distinctStepChanges = LiveDataUtil.distinctUntilChanged(this.store.getStateLiveData(), + (current, next) -> current.getCurrentSetupStep() == next.getCurrentSetupStep() && + current.getAuthenticationCode() == next.getAuthenticationCode()); } public @NonNull LiveData getState() { diff --git a/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkClientThread.java b/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkClientThread.java index e3e703684d..1101505f30 100644 --- a/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkClientThread.java +++ b/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkClientThread.java @@ -69,6 +69,10 @@ final class NetworkClientThread extends Thread { while (shouldKeepRunning()) { Log.i(TAG, "Attempting to connect to server... tries: " + validClientAttemptsRemaining); + resetVerification(); + + boolean transferStarted = false; + try { SelfSignedIdentity.ApprovingTrustManager trustManager = new SelfSignedIdentity.ApprovingTrustManager(); client = (SSLSocket) SelfSignedIdentity.getApprovingSocketFactory(trustManager).createSocket(); @@ -105,6 +109,7 @@ final class NetworkClientThread extends Thread { throw new DeviceTransferAuthentication.DeviceTransferAuthenticationException(e); } + transferStarted = true; handler.sendEmptyMessage(NETWORK_CLIENT_CONNECTED); clientTask.run(context, outputStream); outputStream.flush(); @@ -119,9 +124,14 @@ final class NetworkClientThread extends Thread { success = true; isRunning = false; } catch (IOException e) { - Log.w(TAG, "Error connecting to server", e); - validClientAttemptsRemaining--; - isRunning = validClientAttemptsRemaining > 0; + if (transferStarted) { + Log.w(TAG, "Lost connection after the transfer started, aborting instead of retrying.", e); + isRunning = false; + } else { + Log.w(TAG, "Error connecting to server", e); + validClientAttemptsRemaining--; + isRunning = validClientAttemptsRemaining > 0; + } } } catch (Exception e) { Log.w(TAG, e); @@ -143,6 +153,12 @@ final class NetworkClientThread extends Thread { handler.sendEmptyMessage(NETWORK_CLIENT_STOPPED); } + private void resetVerification() { + synchronized (verificationLock) { + isVerified = null; + } + } + private void awaitAuthenticationCodeVerification() throws DeviceTransferAuthentication.DeviceTransferAuthenticationException { synchronized (verificationLock) { try { diff --git a/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkServerThread.java b/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkServerThread.java index fef6e66637..55cfabcd01 100644 --- a/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkServerThread.java +++ b/lib/device-transfer/src/main/java/org/signal/devicetransfer/NetworkServerThread.java @@ -64,6 +64,9 @@ final class NetworkServerThread extends Thread { handler.sendMessage(handler.obtainMessage(NETWORK_SERVER_STARTED, serverSocket.getLocalPort(), 0)); while (shouldKeepRunning() && !serverSocket.isClosed()) { Log.i(TAG, "Waiting for client socket accept..."); + + boolean transferStarted = false; + try { clientSocket = serverSocket.accept(); @@ -71,6 +74,8 @@ final class NetworkServerThread extends Thread { break; } + resetVerification(); + InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream(); int authenticationCode = DeviceTransferAuthentication.generateServerAuthenticationCode(keys.getX509Encoded(), inputStream, outputStream); @@ -93,16 +98,20 @@ final class NetworkServerThread extends Thread { throw new DeviceTransferAuthentication.DeviceTransferAuthenticationException(e); } + transferStarted = true; handler.sendEmptyMessage(NETWORK_CLIENT_CONNECTED); serverTask.run(context, inputStream); outputStream.write(0x53); outputStream.flush(); } catch (IOException e) { - if (isRunning) { - Log.i(TAG, "Error connecting with client or server socket closed.", e); - } else { + if (!isRunning) { Log.i(TAG, "Server shutting down..."); + } else if (transferStarted) { + Log.w(TAG, "Lost connection after the transfer started, aborting instead of accepting another client.", e); + isRunning = false; + } else { + Log.i(TAG, "Error connecting with client or server socket closed.", e); } } finally { StreamUtil.close(clientSocket); @@ -122,6 +131,12 @@ final class NetworkServerThread extends Thread { handler.sendEmptyMessage(NETWORK_SERVER_STOPPED); } + private void resetVerification() { + synchronized (verificationLock) { + isVerified = null; + } + } + private void awaitAuthenticationCodeVerification() throws DeviceTransferAuthentication.DeviceTransferAuthenticationException { synchronized (verificationLock) { try {