From e76a584bb7a1e045d187326826616ff7eb650558 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 25 Jun 2026 14:57:15 -0400 Subject: [PATCH] Add additional InvalidSessionException checking to 1:1 send flows. --- .../securesms/jobs/IndividualSendJobV2.kt | 7 ++++++ .../api/SignalServiceMessageSender.java | 23 ++++++++++++++++++- .../signal/network/service/MessageService.kt | 5 ++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJobV2.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJobV2.kt index 93e8e07bec..c225aef341 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJobV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJobV2.kt @@ -18,6 +18,7 @@ import org.signal.core.models.ServiceId import org.signal.core.util.logging.Log import org.signal.core.util.orNull import org.signal.libsignal.net.ChallengeOption +import org.signal.libsignal.protocol.InvalidSessionException import org.signal.libsignal.protocol.SignalProtocolAddress import org.signal.network.service.MessageService import org.thoughtcrime.securesms.BuildConfig @@ -339,6 +340,12 @@ class IndividualSendJobV2 private constructor(parameters: Parameters, private va } is MessageService.SendError.ApplicationError -> when (val cause = error.exception) { + // InvalidSessionException is a RuntimeException, must check before fatal runtime check + is InvalidSessionException -> { + Log.w(TAG, "${logPrefix(message.sentTimeMillis)} Session was invalidated mid-send. Retrying.", cause) + Result.retry(nextRunAttemptBackoff(runAttempt + 1)) + } + is RuntimeException -> { Log.e(TAG, "${logPrefix(message.sentTimeMillis)} Encountered a fatal application error. Crash imminent.", cause) Result.fatalFailure(cause) diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index aa3706e601..e4b9a17882 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -25,6 +25,7 @@ import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidRegistrationIdException; +import org.signal.libsignal.protocol.InvalidSessionException; import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SessionBuilder; import org.signal.libsignal.protocol.SignalProtocolAddress; @@ -1971,7 +1972,9 @@ public class SignalServiceMessageSender { { enforceMaxEnvelopeContentSize(content); - long startTime = System.currentTimeMillis(); + long startTime = System.currentTimeMillis(); + boolean retriedInvalidSession = false; + boolean retriedNoSession = false; for (int i = 0; i < RETRY_COUNT; i++) { if (cancelationSignal != null && cancelationSignal.isCanceled()) { @@ -2088,6 +2091,24 @@ public class SignalServiceMessageSender { } catch (StaleDevicesException ste) { Log.w(TAG, "[sendMessage][" + timestamp + "] Handling stale devices. (" + ste.getMessage() + ")"); handleStaleDevices(recipient, ste.getStaleDevices()); + } catch (InvalidSessionException ise) { + if (retriedInvalidSession) { + Log.w(TAG, "[sendMessage][" + timestamp + "] Session still invalid after retry. Archiving sessions to force a rebuild.", ise); + List devices = new ArrayList<>(aciStore.getSubDeviceSessions(recipient.getIdentifier())); + devices.add(SignalServiceAddress.DEFAULT_DEVICE_ID); + archiveSessions(recipient, devices); + } else { + Log.w(TAG, "[sendMessage][" + timestamp + "] Session was invalidated mid-send. Retrying.", ise); + retriedInvalidSession = true; + } + } catch (NoSessionException nse) { + if (retriedNoSession) { + Log.w(TAG, "[sendMessage][" + timestamp + "] Still no session after retry. Rethrowing.", nse); + throw nse; + } + + Log.w(TAG, "[sendMessage][" + timestamp + "] No session mid-send. Retrying.", nse); + retriedNoSession = true; } } diff --git a/lib/network/src/main/java/org/signal/network/service/MessageService.kt b/lib/network/src/main/java/org/signal/network/service/MessageService.kt index 6bdbfa0e41..5f73815fb0 100644 --- a/lib/network/src/main/java/org/signal/network/service/MessageService.kt +++ b/lib/network/src/main/java/org/signal/network/service/MessageService.kt @@ -29,6 +29,7 @@ import org.signal.libsignal.net.UserBasedAuthorization import org.signal.libsignal.net.UserBasedSendAuthorization import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.InvalidKeyException +import org.signal.libsignal.protocol.InvalidSessionException import org.signal.libsignal.protocol.NoSessionException import org.signal.libsignal.protocol.SessionBuilder import org.signal.libsignal.protocol.SignalProtocolAddress @@ -393,6 +394,10 @@ open class MessageService( Log.w(TAG, "Missing or corrupt session for $address. Archiving so the next attempt rebuilds it.", e) protocolStore.archiveSession(address) raise(SendError.ApplicationError(e)) + } catch (e: InvalidSessionException) { + Log.w(TAG, "Invalid session for $address. Archiving so the next attempt rebuilds it.", e) + protocolStore.archiveSession(address) + raise(SendError.ApplicationError(e)) } private fun OutgoingPushMessage.toUnsealedMessage(): SingleOutboundUnsealedMessage {