diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java index 37816635b7..7c9dee6fb7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; @@ -144,6 +145,8 @@ public class AutomaticSessionResetJob extends BaseJob { messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient)); } catch (UntrustedIdentityException e) { Log.w(TAG, "Unable to send null message."); + } catch (NoSessionException e) { + Log.w(TAG, "Unable to send null message; no session to send over.", e); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallUpdateSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallUpdateSendJob.java index bfe094b885..04dcfe143c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallUpdateSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/GroupCallUpdateSendJob.java @@ -4,8 +4,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; - import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.RecipientRecord; @@ -169,7 +169,7 @@ public class GroupCallUpdateSendJob extends BaseJob { } private @NonNull List deliver(@NonNull GroupId groupId, @NonNull List destinations) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { SignalServiceDataMessage.Builder dataMessageBuilder = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.kt index 23f54f7c56..c5ae78037c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/IndividualSendJob.kt @@ -5,6 +5,7 @@ import androidx.annotation.WorkerThread import okio.utf8Size import org.signal.core.util.UuidUtil.parseOrThrow import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.NoSessionException import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil import org.thoughtcrime.securesms.database.NoSuchMessageException import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode @@ -163,7 +164,17 @@ class IndividualSendJob private constructor(parameters: Parameters, private val val profileKey = recipient.profileKey val accessMode = recipient.sealedSenderAccessMode - val unidentified = deliver(message, originalEditedMessage) + val unidentified = try { + deliver(message, originalEditedMessage) + } catch (e: NoSessionException) { + warn(TAG, message.sentTimeMillis.toString(), "Failed to send message, likely due to a missing or corrupt session. Archiving sessions and retrying.", e) + + val recipientId = message.threadRecipient.id + AppDependencies.protocolStore.aci().sessions().archiveSessions(recipientId) + AppDependencies.protocolStore.pni().sessions().archiveSessions(recipientId) + + throw RetryLaterException() + } SignalDatabase.messages.markAsSent(messageId) markAttachmentsUploaded(messageId, message) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java index 3eadf23ffc..43a18f367a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceConfigurationUpdateJob.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JsonJobData; @@ -13,12 +15,10 @@ import org.thoughtcrime.securesms.jobmanager.impl.SealedSenderConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -88,7 +88,7 @@ public class MultiDeviceConfigurationUpdateJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 543f5204dc..c0ecc5a624 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -14,6 +14,9 @@ import org.signal.core.ui.permissions.Permissions; import org.signal.core.util.AppForegroundObserver; import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.IdentityKey; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; +import org.signal.network.service.CdnService; import org.signal.network.exceptions.PushNetworkException; import org.signal.network.service.CdnService; import org.thoughtcrime.securesms.database.RecipientTable; @@ -118,7 +121,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { @Override public void onRun() - throws IOException, UntrustedIdentityException, NetworkException + throws IOException, UntrustedIdentityException, NetworkException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); @@ -139,7 +142,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { } private void generateSingleContactUpdate(@NonNull RecipientId recipientId) - throws IOException, UntrustedIdentityException, NetworkException + throws IOException, UntrustedIdentityException, NetworkException, NoSessionException { WriteDetails writeDetails = createTempFile(); @@ -190,7 +193,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { } private void generateFullContactUpdate() - throws IOException, UntrustedIdentityException, NetworkException + throws IOException, UntrustedIdentityException, NetworkException, NoSessionException { boolean isAppVisible = AppForegroundObserver.isForegrounded(); long timeSinceLastSync = System.currentTimeMillis() - TextSecurePreferences.getLastFullContactSyncTime(context); @@ -277,7 +280,7 @@ public class MultiDeviceContactUpdateJob extends BaseJob { } private void sendUpdate(SignalServiceMessageSender messageSender, InputStream stream, long length, boolean complete) - throws UntrustedIdentityException, NetworkException + throws UntrustedIdentityException, NetworkException, NoSessionException { if (length > 0) { try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java index 3531d6b853..38954d194a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java @@ -5,6 +5,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.RecipientRecord; @@ -17,12 +19,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -97,7 +97,7 @@ public class MultiDeviceMessageRequestResponseJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java index 6c4375dc15..c2f96e690b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceProfileKeyUpdateJob.java @@ -5,9 +5,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; -import org.signal.libsignal.zkgroup.profiles.ProfileKey; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.signal.network.service.CdnService; -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -18,15 +18,14 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; +import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; import java.io.ByteArrayInputStream; @@ -66,7 +65,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java index 22f35046f7..11b40a7da1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceReadUpdateJob.java @@ -3,12 +3,14 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.stream.Collectors; -import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonProperty; +import org.signal.core.models.ServiceId; +import org.signal.core.util.JsonUtils; import org.signal.core.util.ListUtil; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -23,13 +25,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.signal.core.util.JsonUtils; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; -import org.signal.core.models.ServiceId; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -37,6 +36,8 @@ import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class MultiDeviceReadUpdateJob extends BaseJob { @@ -106,7 +107,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java index b27572e44c..93d7a426fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceVerifiedUpdateJob.java @@ -8,6 +8,8 @@ import org.signal.core.util.Base64; import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.jobmanager.Job; @@ -19,13 +21,11 @@ import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -90,7 +90,7 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java index 104b294eed..d1ff52b280 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewOnceOpenJob.java @@ -5,7 +5,10 @@ import androidx.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonProperty; +import org.signal.core.util.JsonUtils; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -19,13 +22,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.signal.core.util.JsonUtils; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -76,7 +76,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewedUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewedUpdateJob.java index 02b1756016..0575469ab4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewedUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceViewedUpdateJob.java @@ -3,12 +3,13 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.stream.Collectors; -import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonProperty; +import org.signal.core.util.JsonUtils; import org.signal.core.util.ListUtil; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; +import org.signal.network.exceptions.PushNetworkException; import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId; import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -23,13 +24,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.NotPushRegisteredException; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.signal.core.util.JsonUtils; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import java.io.IOException; @@ -37,6 +35,8 @@ import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class MultiDeviceViewedUpdateJob extends BaseJob { @@ -106,7 +106,7 @@ public class MultiDeviceViewedUpdateJob extends BaseJob { } @Override - public void onRun() throws IOException, UntrustedIdentityException { + public void onRun() throws IOException, UntrustedIdentityException, NoSessionException { if (!Recipient.self().isRegistered()) { throw new NotPushRegisteredException(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java index 519bb00f61..2f0d297539 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.RecipientRecord; @@ -188,7 +189,7 @@ public class ProfileKeySendJob extends BaseJob { } - private List deliver(@NonNull List destinations) throws IOException, UntrustedIdentityException { + private List deliver(@NonNull List destinations) throws IOException, UntrustedIdentityException, NoSessionException { SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder() .asProfileKeyUpdate(true) .withTimestamp(System.currentTimeMillis()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDistributionListSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDistributionListSendJob.java index 7cb8fb4085..fa63044047 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDistributionListSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDistributionListSendJob.java @@ -6,8 +6,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; - +import org.signal.core.util.Util; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.GroupReceiptTable; @@ -33,7 +34,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.signal.core.util.Util; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -189,7 +189,7 @@ public final class PushDistributionListSendJob extends PushSendJob { PushGroupSendJob.processGroupMessageResults(context, messageId, -1, null, message, results, targets, skipped, existingNetworkFailures, existingIdentityMismatches); - } catch (UntrustedIdentityException | UndeliverableMessageException e) { + } catch (UntrustedIdentityException | UndeliverableMessageException | NoSessionException e) { warn(TAG, String.valueOf(message.getSentTimeMillis()), e); database.markAsSentFailed(messageId); notifyMediaMessageDeliveryFailed(context, messageId); @@ -202,7 +202,7 @@ public final class PushDistributionListSendJob extends PushSendJob { } private List deliver(@NonNull OutgoingMessage message, @NonNull List destinations) - throws IOException, UntrustedIdentityException, UndeliverableMessageException + throws IOException, UntrustedIdentityException, UndeliverableMessageException, NoSessionException { try { List attachments = message.getAttachments().stream().filter(attachment -> !attachment.isSticker()).collect(Collectors.toList()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 753aba39b1..116abc9d55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -6,10 +6,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import java.util.stream.Collectors; - +import org.signal.core.util.ByteUnit; import org.signal.core.util.SetUtil; +import org.signal.core.util.Util; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.database.GroupReceiptTable; import org.thoughtcrime.securesms.database.GroupReceiptTable.GroupReceiptInfo; @@ -44,12 +45,10 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.signal.core.util.ByteUnit; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.MessageUtil; import org.thoughtcrime.securesms.util.RecipientAccessList; import org.thoughtcrime.securesms.util.SignalLocalMetrics; -import org.signal.core.util.Util; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SendMessageResult; @@ -74,9 +73,9 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import kotlin.Pair; - import okio.ByteString; import okio.Utf8; @@ -250,7 +249,7 @@ public final class PushGroupSendJob extends PushSendJob { ConversationShortcutRankingUpdateJob.enqueueForOutgoingIfNecessary(groupRecipient); Log.i(TAG, JobLogger.format(this, "Finished send.")); - } catch (UntrustedIdentityException | UndeliverableMessageException e) { + } catch (UntrustedIdentityException | UndeliverableMessageException | NoSessionException e) { warn(TAG, String.valueOf(message.getSentTimeMillis()), e); database.markAsSentFailed(messageId); notifyMediaMessageDeliveryFailed(context, messageId); @@ -271,7 +270,7 @@ public final class PushGroupSendJob extends PushSendJob { } private List deliver(OutgoingMessage message, @Nullable MessageRecord originalEditedMessage, @NonNull Recipient groupRecipient, @NonNull List destinations) - throws IOException, UntrustedIdentityException, UndeliverableMessageException + throws IOException, UntrustedIdentityException, UndeliverableMessageException, NoSessionException { if (Utf8.size(message.getBody()) > MessageUtil.MAX_INLINE_BODY_SIZE_BYTES) { throw new UndeliverableMessageException("The total body size was greater than our limit of " + MessageUtil.MAX_INLINE_BODY_SIZE_BYTES + " bytes."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java index 2b08beb8d8..86663daa5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSilentUpdateSendJob.java @@ -10,6 +10,7 @@ import org.signal.core.models.ServiceId; import org.signal.core.models.ServiceId.ACI; import org.signal.core.util.Base64; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -178,7 +179,7 @@ public final class PushGroupSilentUpdateSendJob extends BaseJob { } private @NonNull List deliver(@NonNull List destinations, @NonNull GroupId.V2 groupId) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { SignalServiceGroupV2 group = SignalServiceGroupV2.fromProtobuf(groupContextV2); SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java index 9bd0d5a94d..b6c66eaf03 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java @@ -7,6 +7,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.ReactionTable; @@ -229,7 +230,7 @@ public class ReactionSendJob extends BaseJob { } private @NonNull List deliver(@NonNull RecipientRecord conversationRecipient, @NonNull List destinations, @NonNull Recipient targetAuthor, long targetSentTimestamp) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { SignalServiceDataMessage.Builder dataMessageBuilder = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReceiptSender.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReceiptSender.kt index 971213ea6e..01e4a87e57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReceiptSender.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReceiptSender.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.jobs import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.NoSessionException import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.recipients.RecipientId import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException @@ -23,15 +24,15 @@ object ReceiptSender { fun sendWithSessionRepair(recipientId: RecipientId, operation: ReceiptSendOperation): SendMessageResult? { return try { operation.send() - } catch (e: IllegalStateException) { - Log.w(TAG, "Failed to send receipt, likely due to a missing or corrupt session. Archiving sessions and retrying.", e) + } catch (e: NoSessionException) { + Log.w(TAG, "Failed to send receipt due to a missing session. Archiving sessions and retrying.", e) AppDependencies.protocolStore.aci().sessions().archiveSessions(recipientId) AppDependencies.protocolStore.pni().sessions().archiveSessions(recipientId) try { operation.send() - } catch (retryError: IllegalStateException) { + } catch (retryError: NoSessionException) { Log.w(TAG, "Failed to send receipt even after archiving sessions. Dropping.", retryError) null } @@ -39,7 +40,7 @@ object ReceiptSender { } fun interface ReceiptSendOperation { - @Throws(IOException::class, UntrustedIdentityException::class) + @Throws(IOException::class, UntrustedIdentityException::class, NoSessionException::class) fun send(): SendMessageResult } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RemoteDeleteSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RemoteDeleteSendJob.java index e06a12a665..8b52cddede 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RemoteDeleteSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RemoteDeleteSendJob.java @@ -7,6 +7,7 @@ import androidx.annotation.WorkerThread; import org.signal.core.util.SetUtil; import org.signal.core.util.Util; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.NoSuchMessageException; @@ -228,7 +229,7 @@ public class RemoteDeleteSendJob extends BaseJob { long targetSentTimestamp, boolean isForStory, @Nullable DistributionListId distributionListId) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { SignalServiceDataMessage.Builder dataMessageBuilder = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java index 06646efb9e..8445dd2213 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java @@ -5,6 +5,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil; @@ -187,8 +188,8 @@ public class ResendMessageJob extends BaseJob { try { result = messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.ofNullable(groupId).map(GroupId::getDecodedId), urgent); - } catch (IllegalStateException e) { - Log.w(TAG, "Failed to resend content. Archiving session and trying again.", e); + } catch (NoSessionException e) { + Log.w(TAG, "Failed to resend content due to a missing session. Archiving session and trying again.", e); AppDependencies.getProtocolStore().aci().sessions().archiveSessions(recipientId, SignalServiceAddress.DEFAULT_DEVICE_ID); AppDependencies.getProtocolStore().aci().sessions().archiveSiblingSessions(recipient.getServiceId().toProtocolAddress(SignalServiceAddress.DEFAULT_DEVICE_ID)); AppDependencies.getProtocolStore().pni().sessions().archiveSessions(recipientId, SignalServiceAddress.DEFAULT_DEVICE_ID); 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 76b5fe9486..d5bff320ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.signal.core.models.ServiceId; +import org.signal.core.util.Util; import org.signal.core.util.logging.Log; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.protocol.InvalidKeyException; @@ -14,6 +16,7 @@ import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement; import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken; +import org.signal.network.util.Preconditions; import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil; import org.thoughtcrime.securesms.crypto.SenderKeyUtil; import org.thoughtcrime.securesms.database.MessageSendLogTables; @@ -34,7 +37,6 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.RecipientAccessList; import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.SignalLocalMetrics; -import org.signal.core.util.Util; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents; import org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents; @@ -51,10 +53,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; import org.whispersystems.signalservice.api.push.DistributionId; -import org.signal.core.models.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; -import org.signal.network.util.Preconditions; import org.whispersystems.signalservice.internal.push.exceptions.InvalidUnidentifiedAccessHeaderException; import org.whispersystems.signalservice.internal.push.http.CancelationSignal; import org.whispersystems.signalservice.internal.push.http.PartialSendBatchCompleteListener; @@ -108,7 +108,7 @@ public final class GroupSendUtil { boolean isForStory, @Nullable SignalServiceEditMessage editMessage, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Preconditions.checkArgument(groupId == null || distributionListId == null, "Cannot supply both a groupId and a distributionListId!"); @@ -135,7 +135,7 @@ public final class GroupSendUtil { @NonNull SignalServiceDataMessage message, boolean urgent, CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, isRecipientUpdate, false, DataSendOperation.unresendable(message, contentHint, urgent), cancelationSignal); } @@ -152,7 +152,7 @@ public final class GroupSendUtil { @NonNull List allTargets, @NonNull SignalServiceTypingMessage message, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, false, new TypingSendOperation(message), cancelationSignal); } @@ -168,7 +168,7 @@ public final class GroupSendUtil { @NonNull GroupId.V2 groupId, @NonNull List allTargets, @NonNull SignalServiceCallMessage message) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, false, new CallSendOperation(message), null); } @@ -187,7 +187,7 @@ public final class GroupSendUtil { long sentTimestamp, @NonNull SignalServiceStoryMessage message, @NonNull Set manifest) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { return sendMessage( context, @@ -214,7 +214,7 @@ public final class GroupSendUtil { @NonNull MessageId messageId, long sentTimestamp, @NonNull SignalServiceStoryMessage message) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { return sendMessage( context, @@ -253,7 +253,7 @@ public final class GroupSendUtil { boolean isStorySend, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.i(TAG, "Starting group send. GroupId: " + (groupId != null ? groupId.toString() : "none") + ", DistributionId: " + (distributionId != null ? distributionId.toString() : "none") + " RelatedMessageId: " + (relatedMessageId != null ? relatedMessageId.toString() : "none") + ", Targets: " + allTargets.size() + ", RecipientUpdate: " + isRecipientUpdate + ", Operation: " + sendOperation.getClass().getSimpleName()); @@ -577,7 +577,7 @@ public final class GroupSendUtil { boolean isRecipientUpdate, @Nullable PartialSendCompleteListener partialListener, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException; + throws IOException, UntrustedIdentityException, NoSessionException; @NonNull ContentHint getContentHint(); long getSentTimestamp(); @@ -639,7 +639,7 @@ public final class GroupSendUtil { boolean isRecipientUpdate, @Nullable PartialSendCompleteListener partialListener, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { // PniSignatures are only needed for 1:1 messages, but some message jobs use the GroupSendUtil methods to send 1:1 if (targets.size() == 1 && relatedMessageId == null) { @@ -877,7 +877,7 @@ public final class GroupSendUtil { boolean isRecipientUpdate, @Nullable PartialSendCompleteListener partialListener, @Nullable CancelationSignal cancelationSignal) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { // We only allow legacy sends if you're sending to an empty group and just need to send a sync message. if (targets.isEmpty()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java index 3a89dd5b76..7999886f17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -13,16 +13,19 @@ import androidx.annotation.Nullable; import org.greenrobot.eventbus.EventBus; import org.signal.core.models.ServiceId.ACI; +import org.signal.core.util.AppForegroundObserver; import org.signal.core.util.Util; import org.signal.core.util.concurrent.KeyedSerialMonoLifoExecutor; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.zkgroup.GenericServerPublicParams; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialPresentation; import org.signal.libsignal.zkgroup.calllinks.CallLinkSecretParams; import org.signal.libsignal.zkgroup.groups.GroupIdentifier; +import org.signal.network.NetworkResult; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallLinkRootKey; @@ -68,7 +71,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId; import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; -import org.signal.core.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.RecipientAccessList; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.rx.RxStore; @@ -76,7 +78,6 @@ import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager; import org.thoughtcrime.securesms.webrtc.locks.LockManager; import org.webrtc.PeerConnection; -import org.signal.network.NetworkResult; import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -902,6 +903,10 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. } catch (IOException e) { Log.i(TAG, "onSendCallMessage onFailure: ", e); process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), NETWORK_FAILURE)); + } catch (NoSessionException e) { + Log.w(TAG, "onSendCallMessage onFailure: missing or corrupt session. Archiving sessions so the next send rebuilds.", e); + archiveSessions(recipient.getId()); + process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), NETWORK_FAILURE)); } }); } @@ -947,7 +952,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. RetrieveProfileJob.enqueue(identifyFailureRecipientIds, true); } - } catch (UntrustedIdentityException | IOException | InvalidInputException e) { + } catch (UntrustedIdentityException | IOException | InvalidInputException | NoSessionException e) { Log.w(TAG, "onSendCallMessageToGroup failed", e); } }); @@ -1323,10 +1328,23 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. remotePeer.getCallId(), e instanceof UnregisteredUserException ? NO_SUCH_USER : NETWORK_FAILURE, Optional.empty())); + } catch (NoSessionException e) { + Log.w(TAG, "sendCallMessage: missing or corrupt session. Archiving sessions so the next send rebuilds.", e); + archiveSessions(recipient.getId()); + processSendMessageFailureWithChangeDetection(remotePeer, + (s, p) -> p.handleMessageSentError(s, + remotePeer.getCallId(), + NETWORK_FAILURE, + Optional.empty())); } }); } + private void archiveSessions(@NonNull RecipientId recipientId) { + AppDependencies.getProtocolStore().aci().sessions().archiveSessions(recipientId); + AppDependencies.getProtocolStore().pni().sessions().archiveSessions(recipientId); + } + public void sendAcceptedCallEventSyncMessage(@NonNull RemotePeer remotePeer, boolean isOutgoing, boolean isVideoCall) { SignalDatabase .calls() @@ -1337,7 +1355,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. try { SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall); AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent)); - } catch (IOException | UntrustedIdentityException e) { + } catch (IOException | UntrustedIdentityException | NoSessionException e) { Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e); } }); @@ -1354,7 +1372,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. try { SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall); AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent)); - } catch (IOException | UntrustedIdentityException e) { + } catch (IOException | UntrustedIdentityException | NoSessionException e) { Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e); } }); @@ -1367,7 +1385,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall. try { SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, true); AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent)); - } catch (IOException | UntrustedIdentityException e) { + } catch (IOException | UntrustedIdentityException | NoSessionException e) { Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e); } }); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7cbb17417..32310feac9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ androidx-navigation3-core = "1.0.0" androidx-core-telecom = "1.1.0-alpha04" androidx-window = "1.3.0" glide = "4.15.1" -libsignal-client = "0.94.5" +libsignal-client = "0.96.2" mp4parser = "1.9.39" accompanist = "0.28.0" nanohttpd = "2.3.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3dc5cbbc25..2d1c01d957 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -21596,20 +21596,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - - + + 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 5d745398d9..aa3706e601 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 @@ -5,13 +5,14 @@ */ package org.whispersystems.signalservice.api; -import org.signal.network.NetworkResult; - import org.signal.core.models.ServiceId; import org.signal.core.models.ServiceId.PNI; import org.signal.core.util.Base64; import org.signal.core.util.ProtoUtil; +import org.signal.core.util.Uint64RangeException; +import org.signal.core.util.Uint64Util; import org.signal.core.util.UuidUtil; +import org.signal.core.util.logging.Log; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.net.MismatchedDeviceException; import org.signal.libsignal.net.MultiRecipientMessageResponse; @@ -28,13 +29,16 @@ import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SessionBuilder; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.groups.GroupSessionBuilder; -import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.protocol.message.PlaintextContent; import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage; import org.signal.libsignal.protocol.state.PreKeyBundle; import org.signal.libsignal.protocol.state.SessionRecord; import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken; +import org.signal.network.NetworkResult; +import org.signal.network.exceptions.NonSuccessfulResponseCodeException; +import org.signal.network.exceptions.PushNetworkException; +import org.signal.network.util.Preconditions; import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil; import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.EnvelopeContent; @@ -84,9 +88,7 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.push.DistributionId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; -import org.signal.network.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; -import org.signal.network.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RetryNetworkException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; @@ -94,9 +96,6 @@ import org.whispersystems.signalservice.api.push.exceptions.UnknownGroupSendExce import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.AttachmentPointerUtil; import org.whispersystems.signalservice.api.util.CredentialsProvider; -import org.signal.network.util.Preconditions; -import org.signal.core.util.Uint64RangeException; -import org.signal.core.util.Uint64Util; import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException; import org.whispersystems.signalservice.internal.crypto.AttachmentDigest; import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; @@ -234,7 +233,7 @@ public class SignalServiceMessageSender { @Nullable SealedSenderAccess sealedSenderAccess, SignalServiceReceiptMessage message, boolean includePniSignature) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + message.getWhen() + "] Sending a receipt."); @@ -258,7 +257,7 @@ public class SignalServiceMessageSender { @Nullable SealedSenderAccess sealedSenderAccess, Optional groupId, DecryptionErrorMessage errorMessage) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { long timestamp = System.currentTimeMillis(); @@ -312,7 +311,7 @@ public class SignalServiceMessageSender { long timestamp, boolean isRecipientUpdate, Set manifest) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + timestamp + "] Sending a story sync message."); @@ -368,7 +367,7 @@ public class SignalServiceMessageSender { public void sendCallMessage(SignalServiceAddress recipient, @Nullable SealedSenderAccess sealedSenderAccess, SignalServiceCallMessage message) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { long timestamp = System.currentTimeMillis(); Log.d(TAG, "[" + timestamp + "] Sending a call message (single recipient)."); @@ -429,7 +428,7 @@ public class SignalServiceMessageSender { IndividualSendEvents sendEvents, boolean urgent, boolean includePniSignature) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message."); @@ -448,7 +447,7 @@ public class SignalServiceMessageSender { IndividualSendEvents sendEvents, boolean urgent, long targetSentTimestamp) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { Log.d(TAG, "[" + message.getTimestamp() + "] Sending an edit message for " + targetSentTimestamp + "."); @@ -468,7 +467,7 @@ public class SignalServiceMessageSender { boolean urgent, boolean includePniSignature, Content content) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { if (includePniSignature) { Log.d(TAG, "[" + message.getTimestamp() + "] Including PNI signature."); @@ -537,7 +536,7 @@ public class SignalServiceMessageSender { ContentHint contentHint, Optional groupId, boolean urgent) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { Log.d(TAG, "[" + timestamp + "] Resending content."); @@ -613,7 +612,7 @@ public class SignalServiceMessageSender { PartialSendCompleteListener partialListener, CancelationSignal cancelationSignal, boolean urgent) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message to " + recipients.size() + " recipients."); @@ -665,7 +664,7 @@ public class SignalServiceMessageSender { CancelationSignal cancelationSignal, boolean urgent, long targetSentTimestamp) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + message.getTimestamp() + "] Sending a edit message to " + recipients.size() + " recipients."); @@ -702,21 +701,21 @@ public class SignalServiceMessageSender { } public SendMessageResult sendSyncMessage(SignalServiceDataMessage dataMessage) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + dataMessage.getTimestamp() + "] Sending self-sync message."); return sendSyncMessage(createSelfSendSyncMessage(dataMessage)); } public SendMessageResult sendSelfSyncEditMessage(SignalServiceEditMessage editMessage) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Log.d(TAG, "[" + editMessage.getDataMessage().getTimestamp() + "] Sending self-sync edit message for " + editMessage.getTargetSentTimestamp() + "."); return sendSyncMessage(createSelfSendSyncEditMessage(editMessage)); } public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { Content content; boolean urgent = false; @@ -779,7 +778,7 @@ public class SignalServiceMessageSender { } public @Nonnull SendMessageResult sendSyncMessage(Content content, boolean urgent, Optional sent) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { long timestamp = sent.orElseGet(System::currentTimeMillis); @@ -797,7 +796,7 @@ public class SignalServiceMessageSender { * @return Encrypted {@link OutgoingPushMessage} to be included in the change number request sent to the server */ public @Nonnull OutgoingPushMessage getEncryptedSyncPniInitializeDeviceMessage(int deviceId, @Nonnull SyncMessage.PniChangeNumber pniChangeNumber) - throws UntrustedIdentityException, IOException, InvalidKeyException + throws UntrustedIdentityException, IOException, InvalidKeyException, NoSessionException { SyncMessage.Builder syncMessage = createSyncMessageBuilder().pniChangeNumber(pniChangeNumber); Content.Builder content = new Content.Builder().syncMessage(syncMessage.build()); @@ -856,7 +855,7 @@ public class SignalServiceMessageSender { } private SendMessageResult sendVerifiedSyncMessage(VerifiedMessage message) - throws IOException, UntrustedIdentityException + throws IOException, UntrustedIdentityException, NoSessionException { byte[] nullMessageBody = new DataMessage.Builder() .body(Base64.encodeWithPadding(Util.getRandomLengthSecretBytes(140))) @@ -886,7 +885,7 @@ public class SignalServiceMessageSender { } public SendMessageResult sendNullMessage(SignalServiceAddress address, @Nullable SealedSenderAccess sealedSenderAccess) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { byte[] nullMessageBody = new DataMessage.Builder() .body(Base64.encodeWithPadding(Util.getRandomLengthSecretBytes(140))) @@ -1968,7 +1967,7 @@ public class SignalServiceMessageSender { SendEvents sendEvents, boolean urgent, boolean story) - throws UntrustedIdentityException, IOException + throws UntrustedIdentityException, IOException, NoSessionException { enforceMaxEnvelopeContentSize(content); @@ -2827,7 +2826,7 @@ public class SignalServiceMessageSender { boolean online, boolean urgent, boolean story) - throws IOException, InvalidKeyException, UntrustedIdentityException + throws IOException, InvalidKeyException, UntrustedIdentityException, NoSessionException { List messages = new LinkedList<>(); List subDevices = aciStore.getSubDeviceSessions(recipient.getIdentifier()); @@ -2858,7 +2857,7 @@ public class SignalServiceMessageSender { int deviceId, EnvelopeContent plaintext, boolean story) - throws IOException, InvalidKeyException, UntrustedIdentityException + throws IOException, InvalidKeyException, UntrustedIdentityException, NoSessionException { SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getIdentifier(), deviceId); SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, aciStore, sessionLock, null); diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeContent.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeContent.java index 3c8085f942..98c917640f 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeContent.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeContent.java @@ -138,7 +138,7 @@ public interface EnvelopeContent { SignalSealedSessionCipher sealedSessionCipher, SignalProtocolAddress destination, SenderCertificate senderCertificate) - throws UntrustedIdentityException, InvalidKeyException + throws UntrustedIdentityException, InvalidKeyException, NoSessionException { UnidentifiedSenderMessageContent messageContent = new UnidentifiedSenderMessageContent(plaintextContent, senderCertificate, diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java index 6631502772..baea4a12f4 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java @@ -20,12 +20,10 @@ import org.signal.libsignal.protocol.NoSessionException; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.UntrustedIdentityException; import org.signal.libsignal.protocol.state.SessionRecord; -import org.signal.libsignal.protocol.state.SignalProtocolStore; import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; /** @@ -59,7 +57,13 @@ public class SignalSealedSessionCipher { throw new NoSessionException("No session for some recipients"); } - return cipher.multiRecipientEncrypt(recipients, recipientSessions, content); + try { + return cipher.multiRecipientEncrypt(recipients, recipientSessions, content); + } catch (IllegalStateException e) { + NoSessionException nse = new NoSessionException(e.getMessage()); + nse.initCause(e); + throw nse; + } } } @@ -69,13 +73,13 @@ public class SignalSealedSessionCipher { } } - public int getSessionVersion(SignalProtocolAddress remoteAddress) { + public int getSessionVersion(SignalProtocolAddress remoteAddress) throws NoSessionException { try (SignalSessionLock.Lock unused = lock.acquire()) { return cipher.getSessionVersion(remoteAddress); } } - public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) { + public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) throws NoSessionException { try (SignalSessionLock.Lock unused = lock.acquire()) { return cipher.getRemoteRegistrationId(remoteAddress); } diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index e1f242a7da..854be1b6b3 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -6,6 +6,10 @@ package org.whispersystems.signalservice.api.crypto; +import org.signal.core.models.ServiceId; +import org.signal.core.models.ServiceId.ACI; +import org.signal.core.util.UuidUtil; +import org.signal.core.util.logging.Log; import org.signal.libsignal.metadata.InvalidMetadataMessageException; import org.signal.libsignal.metadata.InvalidMetadataVersionException; import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; @@ -27,7 +31,6 @@ import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.InvalidRegistrationIdException; -import org.signal.libsignal.protocol.InvalidSessionException; import org.signal.libsignal.protocol.InvalidVersionException; import org.signal.libsignal.protocol.LegacyMessageException; import org.signal.libsignal.protocol.NoSessionException; @@ -35,7 +38,6 @@ import org.signal.libsignal.protocol.SessionCipher; import org.signal.libsignal.protocol.SignalProtocolAddress; import org.signal.libsignal.protocol.UntrustedIdentityException; import org.signal.libsignal.protocol.groups.GroupCipher; -import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.message.CiphertextMessage; import org.signal.libsignal.protocol.message.PlaintextContent; import org.signal.libsignal.protocol.message.PreKeySignalMessage; @@ -46,10 +48,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountDataStore; import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceMetadata; import org.whispersystems.signalservice.api.push.DistributionId; -import org.signal.core.models.ServiceId; -import org.signal.core.models.ServiceId.ACI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.signal.core.util.UuidUtil; import org.whispersystems.signalservice.internal.push.Content; import org.whispersystems.signalservice.internal.push.Envelope; import org.whispersystems.signalservice.internal.push.OutgoingPushMessage; @@ -115,21 +114,17 @@ public class SignalServiceCipher { public OutgoingPushMessage encrypt(SignalProtocolAddress destination, @Nullable SealedSenderAccess sealedSenderAccess, EnvelopeContent content) - throws UntrustedIdentityException, InvalidKeyException + throws UntrustedIdentityException, InvalidKeyException, NoSessionException { - try { - SignalProtocolAddress localProtocolAddress = new SignalProtocolAddress(localAddress.getIdentifier(), localDeviceId); - SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, localProtocolAddress, destination)); - if (sealedSenderAccess != null) { - SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber() - .orElse(null), localDeviceId)); + SignalProtocolAddress localProtocolAddress = new SignalProtocolAddress(localAddress.getIdentifier(), localDeviceId); + SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, localProtocolAddress, destination)); + if (sealedSenderAccess != null) { + SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber() + .orElse(null), localDeviceId)); - return content.processSealedSender(sessionCipher, sealedSessionCipher, destination, sealedSenderAccess.getSenderCertificate()); - } else { - return content.processUnsealedSender(sessionCipher, destination); - } - } catch (NoSessionException e) { - throw new InvalidSessionException("Session not found: " + destination); + return content.processSealedSender(sessionCipher, sealedSessionCipher, destination, sealedSenderAccess.getSenderCertificate()); + } else { + return content.processUnsealedSender(sessionCipher, destination); } } diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java index 39025315cb..09421d362b 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java @@ -55,7 +55,7 @@ public class SignalSessionCipher { } } - public int getSessionVersion() { + public int getSessionVersion() throws NoSessionException { try (SignalSessionLock.Lock unused = lock.acquire()) { return cipher.getSessionVersion(); } 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 74d571cb3b..6bdbfa0e41 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.NoSessionException import org.signal.libsignal.protocol.SessionBuilder import org.signal.libsignal.protocol.SignalProtocolAddress import org.signal.libsignal.protocol.UntrustedIdentityException @@ -388,6 +389,10 @@ open class MessageService( raise(SendError.IdentityMismatch(serviceId, e)) } catch (e: InvalidKeyException) { raise(SendError.ApplicationError(e)) + } catch (e: NoSessionException) { + Log.w(TAG, "Missing or corrupt session for $address. Archiving so the next attempt rebuilds it.", e) + protocolStore.archiveSession(address) + raise(SendError.ApplicationError(e)) } private fun OutgoingPushMessage.toUnsealedMessage(): SingleOutboundUnsealedMessage { diff --git a/lib/network/src/test/java/org/signal/network/service/MessageServiceTest.kt b/lib/network/src/test/java/org/signal/network/service/MessageServiceTest.kt index 3fc64c8a49..b80b5a029b 100644 --- a/lib/network/src/test/java/org/signal/network/service/MessageServiceTest.kt +++ b/lib/network/src/test/java/org/signal/network/service/MessageServiceTest.kt @@ -28,6 +28,7 @@ import org.signal.libsignal.net.ServiceIdNotFoundException import org.signal.libsignal.net.UserBasedAuthorization import org.signal.libsignal.net.UserBasedSendAuthorization import org.signal.libsignal.protocol.IdentityKeyPair +import org.signal.libsignal.protocol.NoSessionException import org.signal.libsignal.protocol.SessionBuilder import org.signal.libsignal.protocol.SessionCipher import org.signal.libsignal.protocol.SignalProtocolAddress @@ -440,6 +441,21 @@ class MessageServiceTest { assertThat(mismatch.exception).isEqualTo(untrusted) } + @Test + fun `NoSessionException during encryption archives the session and surfaces a retryable error`() = runTest { + val service = newService() + every { protocolStore.getSubDeviceSessions(recipientAci.toString()) } returns emptyList() + val noSession = NoSessionException("missing session") + every { cipher.encrypt(any(), any(), any()) } throws noSession + + val result = service.sendMessage(recipientAci, envelopeContent, timestamp, sealedSenderAccess = null, story = true, isOnline = false) + + verify { protocolStore.archiveSession(SignalProtocolAddress(recipientAci.libSignalServiceId, SignalServiceAddress.DEFAULT_DEVICE_ID)) } + val app = (result as Either.Left).value as MessageService.SendError.ApplicationError + assertThat(app.exception).isEqualTo(noSession) + coVerify(exactly = 0) { messageApi.sendSealedSenderMessage(any(), any(), any(), any(), any(), any()) } + } + @Test fun `content larger than max returns ContentTooLarge before encryption`() = runTest { val largeContent: EnvelopeContent = mockk {