From 42c3f7ead48a3c59645e9b35130445b58183803f Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 4 Nov 2025 11:27:40 -0500 Subject: [PATCH] Use a cancelation result instead of an exception for message sends. --- .../securesms/jobs/TypingSendJob.java | 15 ++++------- .../securesms/messages/GroupSendUtil.java | 16 +++++++++-- .../api/CancelationException.java | 12 --------- .../api/SignalServiceMessageSender.java | 18 ++++++++----- .../api/messages/SendMessageResult.java | 27 +++++++++++++------ .../signalservice/api/svr/SvrBApi.kt | 5 ---- 6 files changed, 49 insertions(+), 44 deletions(-) delete mode 100644 libsignal-service/src/main/java/org/whispersystems/signalservice/api/CancelationException.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 5fba6a5c8e..dea91b82b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.api.CancelationException; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage.Action; @@ -127,15 +126,11 @@ public class TypingSendJob extends BaseJob { SignalServiceTypingMessage typingMessage = new SignalServiceTypingMessage(typing ? Action.STARTED : Action.STOPPED, System.currentTimeMillis(), groupId); - try { - GroupSendUtil.sendTypingMessage(context, - recipient.getGroupId().map(GroupId::requireV2).orElse(null), - recipients, - typingMessage, - this::isCanceled); - } catch (CancelationException e) { - Log.w(TAG, "Canceled during send!"); - } + GroupSendUtil.sendTypingMessage(context, + recipient.getGroupId().map(GroupId::requireV2).orElse(null), + recipients, + typingMessage, + this::isCanceled); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java index c9e69413ba..c608aa9d0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -35,7 +35,6 @@ import org.thoughtcrime.securesms.util.RecipientAccessList; import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.Util; -import org.whispersystems.signalservice.api.CancelationException; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents; import org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents; @@ -446,7 +445,20 @@ public final class GroupSendUtil { } if (cancelationSignal != null && cancelationSignal.isCanceled()) { - throw new CancelationException(); + Log.i(TAG, "Send canceled. Adding canceled results for " + legacyTargets.size() + " remaining legacy targets."); + for (Recipient recipient : legacyTargets) { + allResults.add(SendMessageResult.canceledFailure(recipients.getAddress(recipient.getId()))); + } + + if (unregisteredTargets.size() > 0) { + List unregisteredResults = unregisteredTargets.stream() + .filter(Recipient::getHasServiceId) + .map(t -> SendMessageResult.unregisteredFailure(new SignalServiceAddress(t.requireServiceId(), t.getE164().orElse(null)))) + .collect(Collectors.toList()); + allResults.addAll(unregisteredResults); + } + + return allResults; } boolean onlyTargetIsSelfWithLinkedDevice = legacyTargets.isEmpty() && senderKeyTargets.isEmpty() && SignalStore.account().isMultiDevice(); diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/CancelationException.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/CancelationException.java deleted file mode 100644 index cd1692f092..0000000000 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/CancelationException.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.whispersystems.signalservice.api; - -import java.io.IOException; - -public class CancelationException extends IOException { - public CancelationException() { - } - - public CancelationException(Throwable cause) { - super(cause); - } -} diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 4a213f72c2..d80c7bc9d4 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -1954,7 +1954,7 @@ public class SignalServiceMessageSender { for (int i = 0; i < RETRY_COUNT; i++) { if (cancelationSignal != null && cancelationSignal.isCanceled()) { - throw new CancelationException(); + return SendMessageResult.canceledFailure(recipient); } try { @@ -1976,7 +1976,7 @@ public class SignalServiceMessageSender { } if (cancelationSignal != null && cancelationSignal.isCanceled()) { - throw new CancelationException(); + return SendMessageResult.canceledFailure(recipient); } try { @@ -2015,7 +2015,7 @@ public class SignalServiceMessageSender { } if (cancelationSignal != null && cancelationSignal.isCanceled()) { - throw new CancelationException(); + return SendMessageResult.canceledFailure(recipient); } SendMessageResponse response = socket.sendMessage(messages, sealedSenderAccess, story); @@ -2100,7 +2100,11 @@ public class SignalServiceMessageSender { if (cause instanceof IOException) { throw (IOException) cause; } else if (cause instanceof InterruptedException) { - throw new CancelationException(e); + List canceledResults = new ArrayList<>(recipients.size()); + for (SignalServiceAddress recipient : recipients) { + canceledResults.add(SendMessageResult.canceledFailure(recipient)); + } + return canceledResults; } else { throw e; } @@ -2175,7 +2179,7 @@ public class SignalServiceMessageSender { Single sendWithFallback = messagesSingle .flatMap(messages -> { if (cancelationSignal != null && cancelationSignal.isCanceled()) { - return Single.error(new CancelationException()); + return Single.just(new kotlin.Pair<>(messages, new NetworkResult.NetworkError(new IOException("Canceled")))); } return Single.fromCallable(() -> messageApi.sendMessage(messages, sealedSenderAccess, story)) @@ -2202,7 +2206,7 @@ public class SignalServiceMessageSender { return Single.just(result); } catch (IOException throwable) { if (cancelationSignal != null && cancelationSignal.isCanceled()) { - return Single.error(new CancelationException()); + return Single.just(SendMessageResult.canceledFailure(recipient)); } if (throwable instanceof AuthorizationFailedException || @@ -2248,7 +2252,7 @@ public class SignalServiceMessageSender { return sendWithFallback.onErrorResumeNext(t -> { if (cancelationSignal != null && cancelationSignal.isCanceled()) { - return Single.error(new CancelationException()); + return Single.just(SendMessageResult.canceledFailure(recipient)); } if (retryCount >= RETRY_COUNT) { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SendMessageResult.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SendMessageResult.java index 144207e439..a65ec52bcb 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SendMessageResult.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/SendMessageResult.java @@ -20,33 +20,38 @@ public class SendMessageResult { private final ProofRequiredException proofRequiredFailure; private final RateLimitException rateLimitFailure; private final boolean invalidPreKeyFailure; + private final boolean canceledFailure; public static SendMessageResult success(SignalServiceAddress address, List devices, boolean unidentified, boolean needsSync, long duration, Optional content) { - return new SendMessageResult(address, new Success(unidentified, needsSync, duration, content, devices), false, false, null, null, null, false); + return new SendMessageResult(address, new Success(unidentified, needsSync, duration, content, devices), false, false, null, null, null, false, false); } public static SendMessageResult networkFailure(SignalServiceAddress address) { - return new SendMessageResult(address, null, true, false, null, null, null, false); + return new SendMessageResult(address, null, true, false, null, null, null, false, false); } public static SendMessageResult unregisteredFailure(SignalServiceAddress address) { - return new SendMessageResult(address, null, false, true, null, null, null, false); + return new SendMessageResult(address, null, false, true, null, null, null, false, false); } public static SendMessageResult identityFailure(SignalServiceAddress address, IdentityKey identityKey) { - return new SendMessageResult(address, null, false, false, new IdentityFailure(identityKey), null, null, false); + return new SendMessageResult(address, null, false, false, new IdentityFailure(identityKey), null, null, false, false); } public static SendMessageResult proofRequiredFailure(SignalServiceAddress address, ProofRequiredException proofRequiredException) { - return new SendMessageResult(address, null, false, false, null, proofRequiredException, null, false); + return new SendMessageResult(address, null, false, false, null, proofRequiredException, null, false, false); } public static SendMessageResult rateLimitFailure(SignalServiceAddress address, RateLimitException rateLimitException) { - return new SendMessageResult(address, null, false, false, null, null, rateLimitException, false); + return new SendMessageResult(address, null, false, false, null, null, rateLimitException, false, false); } public static SendMessageResult invalidPreKeyFailure(SignalServiceAddress address) { - return new SendMessageResult(address, null, false, false, null, null, null, true); + return new SendMessageResult(address, null, false, false, null, null, null, true, false); + } + + public static SendMessageResult canceledFailure(SignalServiceAddress address) { + return new SendMessageResult(address, null, false, false, null, null, null, false, true); } public SignalServiceAddress getAddress() { @@ -85,6 +90,10 @@ public class SendMessageResult { return invalidPreKeyFailure; } + public boolean isCanceledFailure() { + return canceledFailure; + } + private SendMessageResult(SignalServiceAddress address, Success success, boolean networkFailure, @@ -92,7 +101,8 @@ public class SendMessageResult { IdentityFailure identityFailure, ProofRequiredException proofRequiredFailure, RateLimitException rateLimitFailure, - boolean invalidPreKeyFailure) + boolean invalidPreKeyFailure, + boolean canceledFailure) { this.address = address; this.success = success; @@ -102,6 +112,7 @@ public class SendMessageResult { this.proofRequiredFailure = proofRequiredFailure; this.rateLimitFailure = rateLimitFailure; this.invalidPreKeyFailure = invalidPreKeyFailure; + this.canceledFailure = canceledFailure; } public static class Success { diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SvrBApi.kt b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SvrBApi.kt index 07b67dfb3e..7472b63f3a 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SvrBApi.kt +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SvrBApi.kt @@ -19,7 +19,6 @@ import org.signal.libsignal.sgxsession.SgxCommunicationFailureException import org.signal.libsignal.svr.DataMissingException import org.signal.libsignal.svr.InvalidSvrBDataException import org.signal.libsignal.svr.RestoreFailedException -import org.whispersystems.signalservice.api.CancelationException import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.backup.MessageBackupKey import org.whispersystems.signalservice.internal.push.AuthCredentials @@ -71,8 +70,6 @@ class SvrBApi(private val network: Network) { is SgxCommunicationFailureException -> StoreResult.SvrError(exception) else -> StoreResult.UnknownError(exception) } - } catch (e: CancelationException) { - StoreResult.UnknownError(e) } catch (e: ExecutionException) { StoreResult.UnknownError(e) } catch (e: InterruptedException) { @@ -107,8 +104,6 @@ class SvrBApi(private val network: Network) { is SgxCommunicationFailureException -> RestoreResult.SvrError(exception) else -> RestoreResult.UnknownError(exception) } - } catch (e: CancelationException) { - RestoreResult.UnknownError(e) } catch (e: ExecutionException) { RestoreResult.UnknownError(e) } catch (e: InterruptedException) {