From 3895578d5188c7d2ef3378d61d7a9aa6efa03928 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 5 Oct 2022 16:25:33 -0400 Subject: [PATCH] Always use sealed sender when sending stories. --- .../AdvancedPrivacySettingsViewModel.kt | 2 +- .../crypto/UnidentifiedAccessUtil.java | 41 ++++-- .../dependencies/ApplicationDependencies.java | 2 +- .../ApplicationDependencyProvider.java | 7 +- .../jobs/CheckServiceReachabilityJob.kt | 4 +- .../securesms/jobs/PushDecryptMessageJob.java | 24 ++++ .../jobs/SenderKeyDistributionSendJob.java | 2 +- .../securesms/messages/GroupSendUtil.java | 26 ++-- .../securesms/messages/RestStrategy.java | 4 +- .../story/StoriesPrivacySettingsRepository.kt | 2 + .../securesms/util/FeatureFlags.java | 5 +- .../securesms/util/SignalProxyUtil.java | 4 +- .../api/SignalServiceMessageReceiver.java | 8 +- .../api/SignalServiceMessageSender.java | 126 +++++++++--------- .../api/services/MessagingService.java | 8 +- .../internal/push/PushServiceSocket.java | 18 +-- .../push/SendGroupMessageResponse.java | 5 +- .../websocket/WebSocketConnection.java | 18 ++- 18 files changed, 184 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt index 84c4f5566..47c7b7b59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsViewModel.kt @@ -77,7 +77,7 @@ class AdvancedPrivacySettingsViewModel( fun setCensorshipCircumventionEnabled(enabled: Boolean) { SignalStore.settings().setCensorshipCircumventionEnabled(enabled) SignalStore.misc().isServiceReachableWithoutCircumvention = false - ApplicationDependencies.resetNetworkConnectionsAfterProxyChange() + ApplicationDependencies.resetAllNetworkConnections() refresh() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java index 2e1bfeb1f..a0a3a4457 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/UnidentifiedAccessUtil.java @@ -17,6 +17,8 @@ import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.keyvalue.CertificateType; import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -68,8 +70,8 @@ public class UnidentifiedAccessUtil { } @WorkerThread - public static Map> getAccessMapFor(@NonNull Context context, @NonNull List recipients) { - List> accessList = getAccessFor(context, recipients, true); + public static Map> getAccessMapFor(@NonNull Context context, @NonNull List recipients, boolean isForStory) { + List> accessList = getAccessFor(context, recipients, true, isForStory); Iterator recipientIterator = recipients.iterator(); Iterator> accessIterator = accessList.iterator(); @@ -82,9 +84,14 @@ public class UnidentifiedAccessUtil { return accessMap; } - + @WorkerThread public static List> getAccessFor(@NonNull Context context, @NonNull List recipients, boolean log) { + return getAccessFor(context, recipients, false, log); + } + + @WorkerThread + public static List> getAccessFor(@NonNull Context context, @NonNull List recipients, boolean isForStory, boolean log) { byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey()); if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) { @@ -96,7 +103,7 @@ public class UnidentifiedAccessUtil { Map typeCounts = new HashMap<>(); for (Recipient recipient : recipients) { - byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); + byte[] theirUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient, isForStory); CertificateType certificateType = getUnidentifiedAccessCertificateType(recipient); byte[] ourUnidentifiedAccessCertificate = SignalStore.certificateValues().getUnidentifiedAccessCertificate(certificateType); @@ -168,28 +175,40 @@ public class UnidentifiedAccessUtil { .getUnidentifiedAccessCertificate(getUnidentifiedAccessCertificateType(recipient)); } - private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient) { + private static @Nullable byte[] getTargetUnidentifiedAccessKey(@NonNull Recipient recipient, boolean isForStory) { ProfileKey theirProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey()); + byte[] accessKey; + switch (recipient.resolve().getUnidentifiedAccessMode()) { case UNKNOWN: if (theirProfileKey == null) { - return UNRESTRICTED_KEY; + accessKey = UNRESTRICTED_KEY; } else { - return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); + accessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); } + break; case DISABLED: - return null; + accessKey = null; + break; case ENABLED: if (theirProfileKey == null) { - return null; + accessKey = null; } else { - return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); + accessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); } + break; case UNRESTRICTED: - return UNRESTRICTED_KEY; + accessKey = UNRESTRICTED_KEY; + break; default: throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode()); } + + if (accessKey == null && isForStory) { + accessKey = UNRESTRICTED_KEY; + } + + return accessKey; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 99a9785bb..b22849d96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -258,7 +258,7 @@ public class ApplicationDependencies { } } - public static void resetNetworkConnectionsAfterProxyChange() { + public static void resetAllNetworkConnections() { synchronized (LOCK) { closeConnections(); if (signalWebSocket != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index 24511d014..8a0989907 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -63,6 +63,7 @@ import org.thoughtcrime.securesms.service.PendingRetryReceiptManager; import org.thoughtcrime.securesms.service.TrimThreadsByDateManager; import org.thoughtcrime.securesms.service.webrtc.SignalCallManager; import org.thoughtcrime.securesms.shakereport.ShakeToReport; +import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.util.AlarmSleepTimer; import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.ByteUnit; @@ -398,7 +399,8 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr signalServiceConfigurationSupplier.get(), Optional.of(new DynamicCredentialsProvider()), BuildConfig.SIGNAL_AGENT, - healthMonitor); + healthMonitor, + Stories.isFeatureEnabled()); } @Override @@ -407,7 +409,8 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr signalServiceConfigurationSupplier.get(), Optional.empty(), BuildConfig.SIGNAL_AGENT, - healthMonitor); + healthMonitor, + Stories.isFeatureEnabled()); } }; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CheckServiceReachabilityJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/CheckServiceReachabilityJob.kt index 90263cc14..eef5f39fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CheckServiceReachabilityJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/CheckServiceReachabilityJob.kt @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.jobmanager.Data import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.stories.Stories import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider import org.whispersystems.signalservice.internal.websocket.WebSocketConnection @@ -78,7 +79,8 @@ class CheckServiceReachabilityJob private constructor(params: Parameters) : Base ), BuildConfig.SIGNAL_AGENT, null, - "" + "", + Stories.isFeatureEnabled() ) try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java index a06d6bf78..f387abb3f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java @@ -102,6 +102,11 @@ public final class PushDecryptMessageJob extends BaseJob { List jobs = new LinkedList<>(); DecryptionResult result = MessageDecryptionUtil.decrypt(context, envelope); + if (result.getState() == MessageState.DECRYPTED_OK && envelope.isStory() && !isStoryMessage(result)) { + Log.w(TAG, "Envelope was flagged as a story, but it did not have any story-related content! Dropping."); + return; + } + if (result.getContent() != null) { if (result.getContent().getSenderKeyDistributionMessage().isPresent()) { handleSenderKeyDistributionMessage(result.getContent().getSender(), result.getContent().getSenderDevice(), result.getContent().getSenderKeyDistributionMessage().get()); @@ -172,6 +177,25 @@ public final class PushDecryptMessageJob extends BaseJob { } } + private boolean isStoryMessage(@NonNull DecryptionResult result) { + if (result.getContent() == null) { + return false; + } + + if (result.getContent().getStoryMessage().isPresent()) { + return true; + } + + if (result.getContent().getDataMessage().isPresent() && + result.getContent().getDataMessage().get().getStoryContext().isPresent() && + result.getContent().getDataMessage().get().getGroupContext().isPresent()) + { + return true; + } + + return false; + } + private boolean needsMigration() { return TextSecurePreferences.getNeedsSqlCipherMigration(context); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java index eaeea0af5..c0fcaa03f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java @@ -120,7 +120,7 @@ public final class SenderKeyDistributionSendJob extends BaseJob { SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId); List> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient)); - SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false).get(0); + SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false, false).get(0); if (result.isSuccess()) { List addresses = result.getSuccess() 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 fbf9886ff..95c27e41a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -94,7 +94,7 @@ public final class GroupSendUtil { boolean urgent) throws IOException, UntrustedIdentityException { - return sendMessage(context, groupId, getDistributionId(groupId), messageId, allTargets, isRecipientUpdate, DataSendOperation.resendable(message, contentHint, messageId, urgent), null); + return sendMessage(context, groupId, getDistributionId(groupId), messageId, allTargets, isRecipientUpdate, false, DataSendOperation.resendable(message, contentHint, messageId, urgent), null); } /** @@ -116,7 +116,7 @@ public final class GroupSendUtil { boolean urgent) throws IOException, UntrustedIdentityException { - return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, isRecipientUpdate, DataSendOperation.unresendable(message, contentHint, urgent), null); + return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, isRecipientUpdate, false, DataSendOperation.unresendable(message, contentHint, urgent), null); } /** @@ -133,7 +133,7 @@ public final class GroupSendUtil { @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException { - return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, new TypingSendOperation(message), cancelationSignal); + return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, false, new TypingSendOperation(message), cancelationSignal); } /** @@ -149,11 +149,11 @@ public final class GroupSendUtil { @NonNull SignalServiceCallMessage message) throws IOException, UntrustedIdentityException { - return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, new CallSendOperation(message), null); + return sendMessage(context, groupId, getDistributionId(groupId), null, allTargets, false, false, new CallSendOperation(message), null); } /** - * Handles all of the logic of sending a story to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of + * Handles all of the logic of sending a story to a distribution list. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of * {@link SendMessageResult}s just like we're used to. * * @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false. @@ -175,6 +175,7 @@ public final class GroupSendUtil { messageId, allTargets, isRecipientUpdate, + true, new StorySendOperation(messageId, null, sentTimestamp, message, manifest), null); } @@ -201,6 +202,7 @@ public final class GroupSendUtil { messageId, allTargets, isRecipientUpdate, + true, new StorySendOperation(messageId, groupId, sentTimestamp, message, Collections.emptySet()), null); } @@ -219,6 +221,7 @@ public final class GroupSendUtil { @Nullable MessageId relatedMessageId, @NonNull List allTargets, boolean isRecipientUpdate, + boolean isStorySend, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException @@ -228,7 +231,7 @@ public final class GroupSendUtil { Set unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet()); List registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList()); - RecipientData recipients = new RecipientData(context, registeredTargets); + RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend); Optional groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty(); List senderKeyTargets = new LinkedList<>(); @@ -257,6 +260,11 @@ public final class GroupSendUtil { Log.i(TAG, "No DistributionId. Using legacy."); legacyTargets.addAll(senderKeyTargets); senderKeyTargets.clear(); + } else if (isStorySend) { + Log.i(TAG, "Sending a story. Using sender key for all " + allTargets.size() + " recipients."); + senderKeyTargets.clear(); + senderKeyTargets.addAll(registeredTargets); + legacyTargets.clear(); } else if (SignalStore.internalValues().removeSenderKeyMinimum()) { Log.i(TAG, "Sender key minimum removed. Using for " + senderKeyTargets.size() + " recipients."); } else if (senderKeyTargets.size() < 2) { @@ -681,7 +689,7 @@ public final class GroupSendUtil { @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException { - return messageSender.sendStory(targets, access, isRecipientUpdate, message, getSentTimestamp(), manifest); + throw new UnsupportedOperationException("Stories can only be send via sender key!"); } @Override @@ -767,8 +775,8 @@ public final class GroupSendUtil { private final Map addressById; private final RecipientAccessList accessList; - RecipientData(@NonNull Context context, @NonNull List recipients) throws IOException { - this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients); + RecipientData(@NonNull Context context, @NonNull List recipients, boolean isForStory) throws IOException { + this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients, isForStory); this.addressById = mapAddresses(context, recipients); this.accessList = new RecipientAccessList(recipients); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java b/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java index 99333ee06..ac5607c60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/RestStrategy.java @@ -10,6 +10,8 @@ import org.thoughtcrime.securesms.jobmanager.JobTracker; import org.thoughtcrime.securesms.jobs.MarkerJob; import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.stories.Stories; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import java.io.IOException; @@ -82,7 +84,7 @@ public class RestStrategy extends MessageRetrievalStrategy { receiver.setSoTimeoutMillis(timeout); - receiver.retrieveMessages(envelope -> { + receiver.retrieveMessages(Stories.isFeatureEnabled(), envelope -> { Log.i(TAG, "Retrieved an envelope." + timeSuffix(startTime)); String jobId = processor.processEnvelope(envelope); diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsRepository.kt index 7e859cd6a..de72a5a4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/settings/story/StoriesPrivacySettingsRepository.kt @@ -5,6 +5,7 @@ import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -25,6 +26,7 @@ class StoriesPrivacySettingsRepository { return Completable.fromAction { SignalStore.storyValues().isFeatureDisabled = !isEnabled Stories.onStorySettingsChanged(Recipient.self().id) + ApplicationDependencies.resetAllNetworkConnections() SignalDatabase.mms.getAllOutgoingStories(false, -1).use { reader -> reader.map { record -> record.id } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 23559335b..a9c1eed64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -249,7 +249,10 @@ public final class FeatureFlags { */ private static final Map FLAG_CHANGE_LISTENERS = new HashMap() {{ put(MESSAGE_PROCESSOR_ALARM_INTERVAL, change -> MessageProcessReceiver.startOrUpdateAlarm(ApplicationDependencies.getApplication())); - put(STORIES, change -> ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob()).then(new RefreshOwnProfileJob()).enqueue()); + put(STORIES, change -> { + ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob()).then(new RefreshOwnProfileJob()).enqueue(); + ApplicationDependencies.resetAllNetworkConnections(); + }); put(GIFT_BADGE_RECEIVE_SUPPORT, change -> ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob()).then(new RefreshOwnProfileJob()).enqueue()); }}; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java index be8d0cc05..f85c7dd88 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java @@ -52,7 +52,7 @@ public final class SignalProxyUtil { public static void enableProxy(@NonNull SignalProxy proxy) { SignalStore.proxy().enableProxy(proxy); Conscrypt.setUseEngineSocketByDefault(true); - ApplicationDependencies.resetNetworkConnectionsAfterProxyChange(); + ApplicationDependencies.resetAllNetworkConnections(); startListeningToWebsocket(); } @@ -63,7 +63,7 @@ public final class SignalProxyUtil { public static void disableProxy() { SignalStore.proxy().disableProxy(); Conscrypt.setUseEngineSocketByDefault(false); - ApplicationDependencies.resetNetworkConnectionsAfterProxyChange(); + ApplicationDependencies.resetAllNetworkConnections(); startListeningToWebsocket(); } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java index 68d9443ff..9199d5ffb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageReceiver.java @@ -195,15 +195,11 @@ public class SignalServiceMessageReceiver { return new SignalServiceStickerManifest(pack.getTitle(), pack.getAuthor(), cover, stickers); } - public List retrieveMessages() throws IOException { - return retrieveMessages(new NullMessageReceivedCallback()); - } - - public List retrieveMessages(MessageReceivedCallback callback) + public List retrieveMessages(boolean allowStories, MessageReceivedCallback callback) throws IOException { List results = new LinkedList<>(); - SignalServiceMessagesResult messageResult = socket.getMessages(); + SignalServiceMessagesResult messageResult = socket.getMessages(allowStories); for (SignalServiceEnvelopeEntity entity : messageResult.getEnvelopes()) { SignalServiceEnvelope envelope; 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 1c3d4bd96..93ac1606c 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 @@ -221,7 +221,7 @@ public class SignalServiceMessageSender { EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - return sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), envelopeContent, false, null, false); + return sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), envelopeContent, false, null, false, false); } /** @@ -237,7 +237,7 @@ public class SignalServiceMessageSender { PlaintextContent content = new PlaintextContent(errorMessage); EnvelopeContent envelopeContent = EnvelopeContent.plaintext(content, groupId); - sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, false); + sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, false, false); } /** @@ -252,7 +252,7 @@ public class SignalServiceMessageSender { Content content = createTypingContent(message); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), envelopeContent, true, null, cancelationSignal, false); + sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), message.getTimestamp(), envelopeContent, true, null, cancelationSignal, false, false); } /** @@ -265,31 +265,12 @@ public class SignalServiceMessageSender { throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException { Content content = createTypingContent(message); - sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, ContentHint.IMPLICIT, message.getGroupId(), true, SenderKeyGroupEvents.EMPTY, false); - } - - public List sendStory(List recipients, - List> unidentifiedAccess, - boolean isRecipientUpdate, - SignalServiceStoryMessage message, - long timestamp, - Set manifest) - throws IOException, UntrustedIdentityException - { - Content content = createStoryContent(message); - EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - List sendMessageResults = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, false); - - if (aciStore.isMultiDevice()) { - SignalServiceSyncMessage syncMessage = createSelfSendSyncMessageForStory(message, timestamp, isRecipientUpdate, manifest); - sendSyncMessage(syncMessage, Optional.empty()); - } - - return sendMessageResults; + sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, ContentHint.IMPLICIT, message.getGroupId(), true, SenderKeyGroupEvents.EMPTY, false, false); } /** - * Send a story using sender key. + * Send a story using sender key. Note: This is not just for group stories -- it's for any story. Just following the naming convention of making sender key + * method named "sendGroup*" */ public List sendGroupStory(DistributionId distributionId, Optional groupId, @@ -302,7 +283,7 @@ public class SignalServiceMessageSender { throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException { Content content = createStoryContent(message); - List sendMessageResults = sendGroupMessage(distributionId, recipients, unidentifiedAccess, timestamp, content, ContentHint.IMPLICIT, groupId, false, SenderKeyGroupEvents.EMPTY, false); + List sendMessageResults = sendGroupMessage(distributionId, recipients, unidentifiedAccess, timestamp, content, ContentHint.IMPLICIT, groupId, false, SenderKeyGroupEvents.EMPTY, false, true); if (aciStore.isMultiDevice()) { SignalServiceSyncMessage syncMessage = createSelfSendSyncMessageForStory(message, timestamp, isRecipientUpdate, manifest); @@ -328,7 +309,7 @@ public class SignalServiceMessageSender { Content content = createCallContent(message); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.DEFAULT, Optional.empty()); - sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, message.isUrgent()); + sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, message.isUrgent(), false); } public List sendCallMessage(List recipients, @@ -339,7 +320,7 @@ public class SignalServiceMessageSender { Content content = createCallContent(message); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.DEFAULT, Optional.empty()); - return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, null, message.isUrgent()); + return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, null, message.isUrgent(), false); } public List sendCallMessage(DistributionId distributionId, @@ -349,7 +330,7 @@ public class SignalServiceMessageSender { throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException { Content content = createCallContent(message); - return sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp().get(), content, ContentHint.IMPLICIT, message.getGroupId(), false, SenderKeyGroupEvents.EMPTY, message.isUrgent()); + return sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp().get(), content, ContentHint.IMPLICIT, message.getGroupId(), false, SenderKeyGroupEvents.EMPTY, message.isUrgent(), false); } /** @@ -399,7 +380,7 @@ public class SignalServiceMessageSender { sendEvents.onMessageEncrypted(); long timestamp = message.getTimestamp(); - SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, urgent); + SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, urgent, false); sendEvents.onMessageSent(); @@ -407,7 +388,7 @@ public class SignalServiceMessageSender { Content syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp, Collections.singletonList(result), false, Collections.emptySet()); EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty()); - sendMessage(localAddress, Optional.empty(), timestamp, syncMessageContent, false, null, false); + sendMessage(localAddress, Optional.empty(), timestamp, syncMessageContent, false, null, false, false); } sendEvents.onSyncMessageSent(); @@ -432,7 +413,8 @@ public class SignalServiceMessageSender { List> unidentifiedAccess, SenderKeyDistributionMessage message, Optional groupId, - boolean urgent) + boolean urgent, + boolean story) throws IOException { ByteString distributionBytes = ByteString.copyFrom(message.serialize()); @@ -441,7 +423,7 @@ public class SignalServiceMessageSender { long timestamp = System.currentTimeMillis(); Log.d(TAG, "[" + timestamp + "] Sending SKDM to " + recipients.size() + " recipients for DistributionId " + distributionId); - return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, urgent); + return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, urgent, story); } /** @@ -466,7 +448,7 @@ public class SignalServiceMessageSender { EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, groupId); Optional access = unidentifiedAccess.isPresent() ? unidentifiedAccess.get().getTargetUnidentifiedAccess() : Optional.empty(); - return sendMessage(address, access, timestamp, envelopeContent, false, null, urgent); + return sendMessage(address, access, timestamp, envelopeContent, false, null, urgent, false); } /** @@ -486,7 +468,7 @@ public class SignalServiceMessageSender { Content content = createMessageContent(message); Optional groupId = message.getGroupId(); - List results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, contentHint, groupId, false, sendEvents, urgent); + List results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, contentHint, groupId, false, sendEvents, urgent, false); sendEvents.onMessageSent(); @@ -494,7 +476,7 @@ public class SignalServiceMessageSender { Content syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.empty(), message.getTimestamp(), results, isRecipientUpdate, Collections.emptySet()); EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty()); - sendMessage(localAddress, Optional.empty(), message.getTimestamp(), syncMessageContent, false, null, false); + sendMessage(localAddress, Optional.empty(), message.getTimestamp(), syncMessageContent, false, null, false, false); } sendEvents.onSyncMessageSent(); @@ -524,7 +506,7 @@ public class SignalServiceMessageSender { Content content = createMessageContent(message); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId()); long timestamp = message.getTimestamp(); - List results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, partialListener, cancelationSignal, urgent); + List results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, partialListener, cancelationSignal, urgent, false); boolean needsSyncInResults = false; sendEvents.onMessageSent(); @@ -545,7 +527,7 @@ public class SignalServiceMessageSender { Content syncMessage = createMultiDeviceSentTranscriptContent(content, recipient, timestamp, results, isRecipientUpdate, Collections.emptySet()); EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty()); - sendMessage(localAddress, Optional.empty(), timestamp, syncMessageContent, false, null, false); + sendMessage(localAddress, Optional.empty(), timestamp, syncMessageContent, false, null, false, false); } sendEvents.onSyncMessageSent(); @@ -608,7 +590,7 @@ public class SignalServiceMessageSender { EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - return sendMessage(localAddress, Optional.empty(), timestamp, envelopeContent, false, null, urgent); + return sendMessage(localAddress, Optional.empty(), timestamp, envelopeContent, false, null, urgent, false); } /** @@ -626,11 +608,7 @@ public class SignalServiceMessageSender { Content.Builder content = Content.newBuilder().setSyncMessage(syncMessage); EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content.build(), ContentHint.IMPLICIT, Optional.empty()); - return getEncryptedMessage(socket, localAddress, Optional.empty(), deviceId, envelopeContent); - } - - public void setSoTimeoutMillis(long soTimeoutMillis) { - socket.setSoTimeoutMillis(soTimeoutMillis); + return getEncryptedMessage(localAddress, Optional.empty(), deviceId, envelopeContent, false); } public void cancelInFlightRequests() { @@ -763,13 +741,13 @@ public class SignalServiceMessageSender { EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - SendMessageResult result = sendMessage(message.getDestination(), Optional.empty(), message.getTimestamp(), envelopeContent, false, null, false); + SendMessageResult result = sendMessage(message.getDestination(), Optional.empty(), message.getTimestamp(), envelopeContent, false, null, false, false); if (result.getSuccess().isNeedsSync()) { Content syncMessage = createMultiDeviceVerifiedContent(message, nullMessage.toByteArray()); EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty()); - sendMessage(localAddress, Optional.empty(), message.getTimestamp(), syncMessageContent, false, null, false); + sendMessage(localAddress, Optional.empty(), message.getTimestamp(), syncMessageContent, false, null, false, false); } return result; @@ -793,7 +771,7 @@ public class SignalServiceMessageSender { EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty()); - return sendMessage(address, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, false); + return sendMessage(address, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, false, false); } private SignalServiceProtos.PniSignatureMessage createPniSignatureMessage() { @@ -1678,7 +1656,8 @@ public class SignalServiceMessageSender { boolean online, PartialSendCompleteListener partialListener, CancelationSignal cancelationSignal, - boolean urgent) + boolean urgent, + boolean story) throws IOException { Log.d(TAG, "[" + timestamp + "] Sending to " + recipients.size() + " recipients."); @@ -1693,7 +1672,7 @@ public class SignalServiceMessageSender { SignalServiceAddress recipient = recipientIterator.next(); Optional access = unidentifiedAccessIterator.next(); futureResults.add(executor.submit(() -> { - SendMessageResult result = sendMessage(recipient, access, timestamp, content, online, cancelationSignal, urgent); + SendMessageResult result = sendMessage(recipient, access, timestamp, content, online, cancelationSignal, urgent, story); if (partialListener != null) { partialListener.onPartialSendComplete(result); } @@ -1761,7 +1740,8 @@ public class SignalServiceMessageSender { EnvelopeContent content, boolean online, CancelationSignal cancelationSignal, - boolean urgent) + boolean urgent, + boolean story) throws UntrustedIdentityException, IOException { enforceMaxContentSize(content); @@ -1774,7 +1754,7 @@ public class SignalServiceMessageSender { } try { - OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, unidentifiedAccess, timestamp, content, online, urgent); + OutgoingPushMessageList messages = getEncryptedMessages(recipient, unidentifiedAccess, timestamp, content, online, urgent, story); if (content.getContent().isPresent() && content.getContent().get().getSyncMessage() != null && content.getContent().get().getSyncMessage().hasSent()) { Log.d(TAG, "[sendMessage][" + timestamp + "] Sending a sent sync message to devices: " + messages.getDevices()); @@ -1788,7 +1768,7 @@ public class SignalServiceMessageSender { if (!unidentifiedAccess.isPresent()) { try { - SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.empty()).blockingGet()).getResultOrThrow(); + SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, Optional.empty(), story).blockingGet()).getResultOrThrow(); return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (InvalidUnidentifiedAccessHeaderException | UnregisteredUserException | MismatchedDevicesException | StaleDevicesException e) { // Non-technical failures shouldn't be retried with socket @@ -1801,7 +1781,7 @@ public class SignalServiceMessageSender { } } else if (unidentifiedAccess.isPresent()) { try { - SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess).blockingGet()).getResultOrThrow(); + SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, unidentifiedAccess, story).blockingGet()).getResultOrThrow(); return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); } catch (InvalidUnidentifiedAccessHeaderException | UnregisteredUserException | MismatchedDevicesException | StaleDevicesException e) { // Non-technical failures shouldn't be retried with socket @@ -1821,7 +1801,7 @@ public class SignalServiceMessageSender { throw new CancelationException(); } - SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess); + SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess, story); return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent()); @@ -1863,7 +1843,8 @@ public class SignalServiceMessageSender { Optional groupId, boolean online, SenderKeyGroupEvents sendEvents, - boolean urgent) + boolean urgent, + boolean story) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException { if (recipients.isEmpty()) { @@ -1900,7 +1881,7 @@ public class SignalServiceMessageSender { }) .collect(Collectors.toList()); - List results = sendSenderKeyDistributionMessage(distributionId, needsSenderKey, access, message, groupId, urgent); + List results = sendSenderKeyDistributionMessage(distributionId, needsSenderKey, access, message, groupId, urgent, story); List successes = results.stream() .filter(SendMessageResult::isSuccess) @@ -1962,7 +1943,7 @@ public class SignalServiceMessageSender { try { try { - SendGroupMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.sendToGroup(ciphertext, joinedUnidentifiedAccess, timestamp, online, urgent).blockingGet()).getResultOrThrow(); + SendGroupMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.sendToGroup(ciphertext, joinedUnidentifiedAccess, timestamp, online, urgent, story).blockingGet()).getResultOrThrow(); return transformGroupResponseToMessageResults(targetInfo.devices, response, content); } catch (InvalidUnidentifiedAccessHeaderException | NotFoundException | GroupMismatchedDevicesException | GroupStaleDevicesException e) { // Non-technical failures shouldn't be retried with socket @@ -1973,7 +1954,7 @@ public class SignalServiceMessageSender { Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Pipe failed, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")"); } - SendGroupMessageResponse response = socket.sendGroupMessage(ciphertext, joinedUnidentifiedAccess, timestamp, online, urgent); + SendGroupMessageResponse response = socket.sendGroupMessage(ciphertext, joinedUnidentifiedAccess, timestamp, online, urgent, true); return transformGroupResponseToMessageResults(targetInfo.devices, response, content); } catch (GroupMismatchedDevicesException e) { Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Handling mismatched devices. (" + e.getMessage() + ")"); @@ -2138,13 +2119,13 @@ public class SignalServiceMessageSender { return builder.build(); } - private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, - SignalServiceAddress recipient, + private OutgoingPushMessageList getEncryptedMessages(SignalServiceAddress recipient, Optional unidentifiedAccess, long timestamp, EnvelopeContent plaintext, boolean online, - boolean urgent) + boolean urgent, + boolean story) throws IOException, InvalidKeyException, UntrustedIdentityException { List messages = new LinkedList<>(); @@ -2161,18 +2142,18 @@ public class SignalServiceMessageSender { for (int deviceId : deviceIds) { if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || aciStore.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) { - messages.add(getEncryptedMessage(socket, recipient, unidentifiedAccess, deviceId, plaintext)); + messages.add(getEncryptedMessage(recipient, unidentifiedAccess, deviceId, plaintext, story)); } } return new OutgoingPushMessageList(recipient.getIdentifier(), timestamp, messages, online, urgent); } - private OutgoingPushMessage getEncryptedMessage(PushServiceSocket socket, - SignalServiceAddress recipient, + private OutgoingPushMessage getEncryptedMessage(SignalServiceAddress recipient, Optional unidentifiedAccess, int deviceId, - EnvelopeContent plaintext) + EnvelopeContent plaintext, + boolean story) throws IOException, InvalidKeyException, UntrustedIdentityException { SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getIdentifier(), deviceId); @@ -2180,7 +2161,7 @@ public class SignalServiceMessageSender { if (!aciStore.containsSession(signalProtocolAddress)) { try { - List preKeys = socket.getPreKeys(recipient, unidentifiedAccess, deviceId); + List preKeys = getPreKeys(recipient, unidentifiedAccess, deviceId, story); for (PreKeyBundle preKey : preKeys) { try { @@ -2207,6 +2188,19 @@ public class SignalServiceMessageSender { } } + + private List getPreKeys(SignalServiceAddress recipient, Optional unidentifiedAccess, int deviceId, boolean story) throws IOException { + try { + return socket.getPreKeys(recipient, unidentifiedAccess, deviceId); + } catch (NonSuccessfulResponseCodeException e) { + if (e.getCode() == 401 && story) { + return socket.getPreKeys(recipient, Optional.empty(), deviceId); + } else { + throw e; + } + } + } + private void handleMismatchedDevices(PushServiceSocket socket, SignalServiceAddress recipient, MismatchedDevices mismatchedDevices) throws IOException, UntrustedIdentityException diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java index c52355c0f..9d4e63a14 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/MessagingService.java @@ -43,7 +43,7 @@ public class MessagingService { this.signalWebSocket = signalWebSocket; } - public Single> send(OutgoingPushMessageList list, Optional unidentifiedAccess) { + public Single> send(OutgoingPushMessageList list, Optional unidentifiedAccess, boolean story) { List headers = new LinkedList() {{ add("content-type:application/json"); }}; @@ -51,7 +51,7 @@ public class MessagingService { WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() .setId(new SecureRandom().nextLong()) .setVerb("PUT") - .setPath(String.format("/v1/messages/%s", list.getDestination())) + .setPath(String.format("/v1/messages/%s?story=%s", list.getDestination(), story ? "true" : "false")) .addAllHeaders(headers) .setBody(ByteString.copyFrom(JsonUtil.toJson(list).getBytes())) .build(); @@ -72,13 +72,13 @@ public class MessagingService { .onErrorReturn(ServiceResponse::forUnknownError); } - public Single> sendToGroup(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent) { + public Single> sendToGroup(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent, boolean story) { List headers = new LinkedList() {{ add("content-type:application/vnd.signal-messenger.mrm"); add("Unidentified-Access-Key:" + Base64.encodeBytes(joinedUnidentifiedAccess)); }}; - String path = String.format(Locale.US, "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s", timestamp, online, urgent); + String path = String.format(Locale.US, "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s&story=%s", timestamp, online, urgent, story); WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() .setId(new SecureRandom().nextLong()) diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 5670ea80c..43d1cdcd0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -220,8 +220,8 @@ public class PushServiceSocket { private static final String DEVICE_PATH = "/v1/devices/%s"; private static final String DIRECTORY_AUTH_PATH = "/v1/directory/auth"; - private static final String MESSAGE_PATH = "/v1/messages/%s"; - private static final String GROUP_MESSAGE_PATH = "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s"; + private static final String MESSAGE_PATH = "/v1/messages/%s?story=%s"; + private static final String GROUP_MESSAGE_PATH = "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s&story=%s"; private static final String SENDER_ACK_MESSAGE_PATH = "/v1/messages/%s/%d"; private static final String UUID_ACK_MESSAGE_PATH = "/v1/messages/uuid/%s"; private static final String ATTACHMENT_V2_PATH = "/v2/attachments/form/upload"; @@ -486,12 +486,12 @@ public class PushServiceSocket { return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate(); } - public SendGroupMessageResponse sendGroupMessage(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent) + public SendGroupMessageResponse sendGroupMessage(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent, boolean story) throws IOException { ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random); - String path = String.format(Locale.US, GROUP_MESSAGE_PATH, timestamp, online, urgent); + String path = String.format(Locale.US, GROUP_MESSAGE_PATH, timestamp, online, urgent, story); Request.Builder requestBuilder = new Request.Builder(); requestBuilder.url(String.format("%s%s", connectionHolder.getUrl(), path)); @@ -544,11 +544,11 @@ public class PushServiceSocket { } } - public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional unidentifiedAccess) + public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional unidentifiedAccess, boolean story) throws IOException { try { - String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); + String responseText = makeServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination(), story ? "true" : "false"), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess); SendMessageResponse response = JsonUtil.fromJson(responseText, SendMessageResponse.class); response.setSentUnidentfied(unidentifiedAccess.isPresent()); @@ -559,8 +559,10 @@ public class PushServiceSocket { } } - public SignalServiceMessagesResult getMessages() throws IOException { - try (Response response = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", (RequestBody) null, NO_HEADERS, NO_HANDLER, Optional.empty())) { + public SignalServiceMessagesResult getMessages(boolean allowStories) throws IOException { + Map headers = Collections.singletonMap("X-Signal-Receive-Stories", allowStories ? "true" : "false"); + + try (Response response = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", (RequestBody) null, headers, NO_HANDLER, Optional.empty())) { validateServiceResponse(response); List envelopes = readBodyJson(response.body(), SignalServiceEnvelopeEntityList.class).getMessages(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java index 0d7856880..25110f357 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/SendGroupMessageResponse.java @@ -18,9 +18,10 @@ public class SendGroupMessageResponse { public SendGroupMessageResponse() {} public Set getUnsentTargets() { - Set serviceIds = new HashSet<>(uuids404.length); + String[] uuids = uuids404 != null ? uuids404 : new String[0]; + Set serviceIds = new HashSet<>(uuids.length); - for (String raw : uuids404) { + for (String raw : uuids) { ServiceId parsed = ServiceId.parseOrNull(raw); if (parsed != null) { serviceIds.add(parsed); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java index 2f722427f..0b2e3509c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/websocket/WebSocketConnection.java @@ -66,14 +66,15 @@ public class WebSocketConnection extends WebSocketListener { private final String name; private final String wsUri; - private final TrustStore trustStore; - private final Optional credentialsProvider; - private final String signalAgent; + private final TrustStore trustStore; + private final Optional credentialsProvider; + private final String signalAgent; private final HealthMonitor healthMonitor; private final List interceptors; private final Optional dns; private final Optional signalProxy; private final BehaviorSubject webSocketState; + private final boolean allowStories; private WebSocket client; @@ -81,8 +82,9 @@ public class WebSocketConnection extends WebSocketListener { SignalServiceConfiguration serviceConfiguration, Optional credentialsProvider, String signalAgent, - HealthMonitor healthMonitor) { - this(name, serviceConfiguration, credentialsProvider, signalAgent, healthMonitor, ""); + HealthMonitor healthMonitor, + boolean allowStories) { + this(name, serviceConfiguration, credentialsProvider, signalAgent, healthMonitor, "", allowStories); } public WebSocketConnection(String name, @@ -90,7 +92,8 @@ public class WebSocketConnection extends WebSocketListener { Optional credentialsProvider, String signalAgent, HealthMonitor healthMonitor, - String extraPathUri) + String extraPathUri, + boolean allowStories) { this.name = "[" + name + ":" + System.identityHashCode(this) + "]"; this.trustStore = serviceConfiguration.getSignalServiceUrls()[0].getTrustStore(); @@ -101,6 +104,7 @@ public class WebSocketConnection extends WebSocketListener { this.signalProxy = serviceConfiguration.getSignalProxy(); this.healthMonitor = healthMonitor; this.webSocketState = BehaviorSubject.createDefault(WebSocketConnectionState.DISCONNECTED); + this.allowStories = allowStories; String uri = serviceConfiguration.getSignalServiceUrls()[0].getUrl().replace("https://", "wss://").replace("http://", "ws://"); @@ -156,6 +160,8 @@ public class WebSocketConnection extends WebSocketListener { requestBuilder.addHeader("X-Signal-Agent", signalAgent); } + requestBuilder.addHeader("X-Signal-Receive-Stories", allowStories ? "true" : "false"); + webSocketState.onNext(WebSocketConnectionState.CONNECTING); this.client = okHttpClient.newWebSocket(requestBuilder.build(), this);