Add Group Send Endorsements support.
This commit is contained in:
parent
414368e251
commit
f5abd7acdf
86 changed files with 1691 additions and 887 deletions
|
@ -314,7 +314,7 @@ class GroupTableTest {
|
|||
.revision(0)
|
||||
.build()
|
||||
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState)!!
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
|
||||
}
|
||||
|
||||
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
|
||||
|
@ -339,6 +339,6 @@ class GroupTableTest {
|
|||
.revision(0)
|
||||
.build()
|
||||
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState)!!
|
||||
return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.signal.libsignal.protocol.ecc.Curve
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
|
||||
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.testing.AliceClient
|
||||
|
@ -55,8 +55,8 @@ class MessageProcessingPerformanceTest {
|
|||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(UnidentifiedAccessUtil::class)
|
||||
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
|
||||
mockkStatic(SealedSenderAccessUtil::class)
|
||||
every { SealedSenderAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
|
||||
|
||||
mockkObject(MessageContentProcessor)
|
||||
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
|
||||
|
@ -64,7 +64,7 @@ class MessageProcessingPerformanceTest {
|
|||
|
||||
@After
|
||||
fun after() {
|
||||
unmockkStatic(UnidentifiedAccessUtil::class)
|
||||
unmockkStatic(SealedSenderAccessUtil::class)
|
||||
unmockkStatic(MessageContentProcessor::class)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.testing
|
|||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore
|
||||
|
@ -50,7 +49,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
|
|||
fun encrypt(now: Long, destination: Recipient): Envelope {
|
||||
return AppDependencies.signalServiceMessageSender.getEncryptedMessage(
|
||||
SignalServiceAddress(destination.requireServiceId(), destination.requireE164()),
|
||||
FakeClientHelpers.getTargetUnidentifiedAccess(ProfileKeyUtil.getSelfProfileKey(), ProfileKey(destination.profileKey), aliceSenderCertificate),
|
||||
FakeClientHelpers.getSealedSenderAccess(ProfileKey(destination.profileKey), aliceSenderCertificate),
|
||||
1,
|
||||
FakeClientHelpers.encryptedTextMessage(now),
|
||||
false
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
|||
import org.signal.libsignal.protocol.util.KeyHelper
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.SignedPreKeyTable
|
||||
|
@ -25,14 +25,13 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|||
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
|
||||
import org.whispersystems.signalservice.api.SignalSessionLock
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
|
||||
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.internal.push.Envelope
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
|
@ -75,7 +74,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
|||
}
|
||||
|
||||
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
|
||||
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
|
||||
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, SealedSenderAccessUtil.getCertificateValidator())
|
||||
cipher.decrypt(envelope, serverDeliveredTimestamp)
|
||||
}
|
||||
|
||||
|
@ -126,8 +125,8 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
|
|||
return ProfileKeyUtil.getSelfProfileKey()
|
||||
}
|
||||
|
||||
private fun getAliceUnidentifiedAccess(): Optional<UnidentifiedAccess> {
|
||||
return FakeClientHelpers.getTargetUnidentifiedAccess(profileKey, getAliceProfileKey(), senderCertificate)
|
||||
private fun getAliceUnidentifiedAccess(): SealedSenderAccess? {
|
||||
return FakeClientHelpers.getSealedSenderAccess(getAliceProfileKey(), senderCertificate)
|
||||
}
|
||||
|
||||
private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore {
|
||||
|
|
|
@ -14,8 +14,8 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
|||
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.internal.push.Content
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
|
@ -46,11 +46,10 @@ object FakeClientHelpers {
|
|||
}
|
||||
}
|
||||
|
||||
fun getTargetUnidentifiedAccess(myProfileKey: ProfileKey, theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): Optional<UnidentifiedAccess> {
|
||||
val selfUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(myProfileKey)
|
||||
val themUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey)
|
||||
fun getSealedSenderAccess(theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): SealedSenderAccess? {
|
||||
val themUnidentifiedAccessKey = UnidentifiedAccess(UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey), senderCertificate.serialized, false)
|
||||
|
||||
return UnidentifiedAccessPair(UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate.serialized, false), UnidentifiedAccess(themUnidentifiedAccessKey, senderCertificate.serialized, false)).targetUnidentifiedAccess
|
||||
return SealedSenderAccess.forIndividual(themUnidentifiedAccessKey)
|
||||
}
|
||||
|
||||
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
|
||||
|
|
|
@ -33,7 +33,7 @@ object GroupTestingUtils {
|
|||
.title(MessageContentFuzzer.string())
|
||||
.build()
|
||||
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState, null)!!
|
||||
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
|
||||
SignalDatabase.recipients.setProfileSharing(groupRecipientId, true)
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendEndorsementInternalNotifier;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
|
@ -222,6 +223,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
|||
.addPostRender(GroupRingCleanupJob::enqueue)
|
||||
.addPostRender(LinkedDeviceInactiveCheckJob::enqueueIfNecessary)
|
||||
.addPostRender(() -> ActiveCallManager.clearNotifications(this))
|
||||
.addPostRender(() -> GroupSendEndorsementInternalNotifier.init())
|
||||
.execute();
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
|
|
|
@ -241,7 +241,7 @@ fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId {
|
|||
}
|
||||
|
||||
val recipientId = writableDatabase.insert(RecipientTable.TABLE_NAME, null, values)
|
||||
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState)
|
||||
val restoredId = SignalDatabase.groups.create(masterKey, decryptedState, groupSendEndorsements = null)
|
||||
if (restoredId != null) {
|
||||
SignalDatabase.groups.setShowAsStoryState(restoredId, group.storySendMode.toGroupShowAsStoryState())
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
|
|||
|
||||
textPref(
|
||||
title = DSLSettingsText.from("Sealed Sender Mode"),
|
||||
summary = DSLSettingsText.from(recipient.unidentifiedAccessMode.toString())
|
||||
summary = DSLSettingsText.from(recipient.sealedSenderAccessMode.toString())
|
||||
)
|
||||
|
||||
textPref(
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
|
@ -21,10 +21,8 @@ import org.thoughtcrime.securesms.keyvalue.CertificateType;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.signal.core.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
@ -35,9 +33,9 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UnidentifiedAccessUtil {
|
||||
public class SealedSenderAccessUtil {
|
||||
|
||||
private static final String TAG = Log.tag(UnidentifiedAccessUtil.class);
|
||||
private static final String TAG = Log.tag(SealedSenderAccessUtil.class);
|
||||
|
||||
private static final byte[] UNRESTRICTED_KEY = new byte[16];
|
||||
|
||||
|
@ -46,28 +44,34 @@ public class UnidentifiedAccessUtil {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
return getAccessFor(context, recipient, true);
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient) {
|
||||
return getSealedSenderAccessFor(recipient, true);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient, boolean log) {
|
||||
return getAccessFor(context, Collections.singletonList(recipient), log).get(0);
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, boolean log) {
|
||||
return SealedSenderAccess.forIndividual(getAccessFor(recipient, log));
|
||||
}
|
||||
|
||||
public static @Nullable SealedSenderAccess getSealedSenderAccessFor(@NonNull Recipient recipient, @Nullable SealedSenderAccess.CreateGroupSendToken createGroupSendToken) {
|
||||
return SealedSenderAccess.forIndividualWithGroupFallback(getAccessFor(recipient, true), getSealedSenderCertificate(), createGroupSendToken);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients) {
|
||||
return getAccessFor(context, recipients, true);
|
||||
private static @Nullable UnidentifiedAccess getAccessFor(@NonNull Recipient recipient, boolean log) {
|
||||
return getAccessFor(Collections.singletonList(recipient), false, log)
|
||||
.get(0)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static Map<RecipientId, Optional<UnidentifiedAccessPair>> getAccessMapFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) {
|
||||
List<Optional<UnidentifiedAccessPair>> accessList = getAccessFor(context, recipients, isForStory, true);
|
||||
public static Map<RecipientId, Optional<UnidentifiedAccess>> getAccessMapFor(@NonNull List<Recipient> recipients, boolean isForStory) {
|
||||
List<Optional<UnidentifiedAccess>> accessList = getAccessFor(recipients, isForStory, true);
|
||||
|
||||
Iterator<Recipient> recipientIterator = recipients.iterator();
|
||||
Iterator<Optional<UnidentifiedAccessPair>> accessIterator = accessList.iterator();
|
||||
Iterator<Recipient> recipientIterator = recipients.iterator();
|
||||
Iterator<Optional<UnidentifiedAccess>> accessIterator = accessList.iterator();
|
||||
|
||||
Map<RecipientId, Optional<UnidentifiedAccessPair>> accessMap = new HashMap<>(recipients.size());
|
||||
Map<RecipientId, Optional<UnidentifiedAccess>> accessMap = new HashMap<>(recipients.size());
|
||||
|
||||
while (recipientIterator.hasNext()) {
|
||||
accessMap.put(recipientIterator.next().getId(), accessIterator.next());
|
||||
|
@ -77,40 +81,22 @@ public class UnidentifiedAccessUtil {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
|
||||
return getAccessFor(context, recipients, false, log);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
|
||||
final byte[] ourUnidentifiedAccessKey;
|
||||
|
||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
|
||||
} else {
|
||||
ourUnidentifiedAccessKey = ProfileKeyUtil.getSelfProfileKey().deriveAccessKey();
|
||||
}
|
||||
|
||||
private static List<Optional<UnidentifiedAccess>> getAccessFor(@NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
|
||||
CertificateType certificateType = getUnidentifiedAccessCertificateType();
|
||||
byte[] ourUnidentifiedAccessCertificate = SignalStore.certificate().getUnidentifiedAccessCertificate(certificateType);
|
||||
|
||||
List<Optional<UnidentifiedAccessPair>> access = recipients.parallelStream().map(recipient -> {
|
||||
UnidentifiedAccessPair unidentifiedAccessPair = null;
|
||||
List<Optional<UnidentifiedAccess>> access = recipients.parallelStream().map(recipient -> {
|
||||
UnidentifiedAccess unidentifiedAccess = null;
|
||||
if (ourUnidentifiedAccessCertificate != null) {
|
||||
try {
|
||||
UnidentifiedAccess theirAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
|
||||
UnidentifiedAccess ourAccess = new UnidentifiedAccess(ourUnidentifiedAccessKey, ourUnidentifiedAccessCertificate, false);
|
||||
|
||||
if (theirAccess != null) {
|
||||
unidentifiedAccessPair = new UnidentifiedAccessPair(theirAccess, ourAccess);
|
||||
}
|
||||
unidentifiedAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
|
||||
} catch (InvalidCertificateException e) {
|
||||
Log.w(TAG, "Invalid unidentified access certificate!", e);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Missing unidentified access certificate!");
|
||||
Log.w(TAG, "Missing our unidentified access certificate!");
|
||||
}
|
||||
return Optional.ofNullable(unidentifiedAccessPair);
|
||||
return Optional.ofNullable(unidentifiedAccess);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
int unidentifiedCount = Stream.of(access).filter(Optional::isPresent).toList().size();
|
||||
|
@ -123,28 +109,17 @@ public class UnidentifiedAccessUtil {
|
|||
return access;
|
||||
}
|
||||
|
||||
public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
|
||||
public static @Nullable SenderCertificate getSealedSenderCertificate() {
|
||||
byte[] unidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
|
||||
if (unidentifiedAccessCertificate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
|
||||
|
||||
if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
|
||||
ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
|
||||
}
|
||||
|
||||
if (ourUnidentifiedAccessCertificate != null) {
|
||||
return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||
ourUnidentifiedAccessCertificate,
|
||||
false),
|
||||
new UnidentifiedAccess(ourUnidentifiedAccessKey,
|
||||
ourUnidentifiedAccessCertificate,
|
||||
false)));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
return new SenderCertificate(unidentifiedAccessCertificate);
|
||||
} catch (InvalidCertificateException e) {
|
||||
Log.w(TAG, e);
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +141,7 @@ public class UnidentifiedAccessUtil {
|
|||
|
||||
byte[] accessKey;
|
||||
|
||||
switch (recipient.resolve().getUnidentifiedAccessMode()) {
|
||||
switch (recipient.resolve().getSealedSenderAccessMode()) {
|
||||
case UNKNOWN:
|
||||
if (theirProfileKey == null) {
|
||||
if (isForStory) {
|
||||
|
@ -192,7 +167,7 @@ public class UnidentifiedAccessUtil {
|
|||
accessKey = UNRESTRICTED_KEY;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode());
|
||||
throw new AssertionError("Unknown mode: " + recipient.getSealedSenderAccessMode().getMode());
|
||||
}
|
||||
|
||||
if (accessKey == null && isForStory) {
|
|
@ -19,7 +19,9 @@ import org.signal.core.util.isAbsent
|
|||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.optionalString
|
||||
import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToMap
|
||||
import org.signal.core.util.readToSingleInt
|
||||
import org.signal.core.util.readToSingleLong
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
|
@ -30,7 +32,11 @@ import org.signal.core.util.requireString
|
|||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember
|
||||
|
@ -39,6 +45,7 @@ import org.thoughtcrime.securesms.contacts.paged.collections.ContactSearchIterat
|
|||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.recipients
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord
|
||||
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
|
@ -50,6 +57,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.groupsv2.findMemberByAci
|
||||
import org.whispersystems.signalservice.api.groupsv2.findPendingByServiceId
|
||||
import org.whispersystems.signalservice.api.groupsv2.findRequestingByAci
|
||||
|
@ -63,6 +71,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
|||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import java.io.Closeable
|
||||
import java.security.SecureRandom
|
||||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
import java.util.stream.Collectors
|
||||
import javax.annotation.CheckReturnValue
|
||||
|
@ -94,6 +103,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
const val DISTRIBUTION_ID = "distribution_id"
|
||||
const val SHOW_AS_STORY_STATE = "show_as_story_state"
|
||||
const val LAST_FORCE_UPDATE_TIMESTAMP = "last_force_update_timestamp"
|
||||
const val GROUP_SEND_ENDORSEMENTS_EXPIRATION = "group_send_endorsements_expiration"
|
||||
|
||||
/** 32 bytes serialized [GroupMasterKey] */
|
||||
const val V2_MASTER_KEY = "master_key"
|
||||
|
@ -125,12 +135,13 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
$UNMIGRATED_V1_MEMBERS TEXT DEFAULT NULL,
|
||||
$DISTRIBUTION_ID TEXT UNIQUE DEFAULT NULL,
|
||||
$SHOW_AS_STORY_STATE INTEGER DEFAULT ${ShowAsStoryState.IF_ACTIVE.code},
|
||||
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0
|
||||
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$GROUP_SEND_ENDORSEMENTS_EXPIRATION INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
@JvmField
|
||||
val CREATE_INDEXS = MembershipTable.CREATE_INDEXES
|
||||
val CREATE_INDEXS: Array<String> = MembershipTable.CREATE_INDEXES
|
||||
|
||||
private val GROUP_PROJECTION = arrayOf(
|
||||
GROUP_ID,
|
||||
|
@ -147,7 +158,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
V2_MASTER_KEY,
|
||||
V2_REVISION,
|
||||
V2_DECRYPTED_GROUP,
|
||||
LAST_FORCE_UPDATE_TIMESTAMP
|
||||
LAST_FORCE_UPDATE_TIMESTAMP,
|
||||
GROUP_SEND_ENDORSEMENTS_EXPIRATION
|
||||
)
|
||||
|
||||
val TYPED_GROUP_PROJECTION = GROUP_PROJECTION
|
||||
|
@ -165,6 +177,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
const val ID = "_id"
|
||||
const val GROUP_ID = "group_id"
|
||||
const val RECIPIENT_ID = "recipient_id"
|
||||
const val ENDORSEMENT = "endorsement"
|
||||
|
||||
//language=sql
|
||||
const val CREATE_TABLE = """
|
||||
|
@ -172,6 +185,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
$ID INTEGER PRIMARY KEY,
|
||||
$GROUP_ID TEXT NOT NULL REFERENCES ${GroupTable.TABLE_NAME} (${GroupTable.GROUP_ID}) ON DELETE CASCADE,
|
||||
$RECIPIENT_ID INTEGER NOT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$ENDORSEMENT BLOB DEFAULT NULL,
|
||||
UNIQUE($GROUP_ID, $RECIPIENT_ID)
|
||||
)
|
||||
"""
|
||||
|
@ -568,19 +582,19 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
throw LegacyGroupInsertException(groupId)
|
||||
}
|
||||
|
||||
return create(groupId, title, members, avatar, null, null)
|
||||
return create(groupId, title, members, avatar, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupId: GroupId.Mms, title: String?, members: Collection<RecipientId>): Boolean {
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null)
|
||||
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup): GroupId.V2? {
|
||||
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?): GroupId.V2? {
|
||||
val groupId = GroupId.v2(groupMasterKey)
|
||||
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState)) {
|
||||
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements)) {
|
||||
groupId
|
||||
} else {
|
||||
null
|
||||
|
@ -604,10 +618,9 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
if (updated < 1) {
|
||||
Log.w(TAG, "No group entry. Creating restore placeholder for $groupId")
|
||||
create(
|
||||
groupMasterKey,
|
||||
DecryptedGroup.Builder()
|
||||
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build()
|
||||
groupMasterKey = groupMasterKey,
|
||||
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "Had a group entry, but it was missing a master key. Updated.")
|
||||
|
@ -628,7 +641,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
memberCollection: Collection<RecipientId>,
|
||||
avatar: SignalServiceAttachmentPointer?,
|
||||
groupMasterKey: GroupMasterKey?,
|
||||
groupState: DecryptedGroup?
|
||||
groupState: DecryptedGroup?,
|
||||
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
): Boolean {
|
||||
val membershipValues = mutableListOf<ContentValues>()
|
||||
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
|
@ -640,7 +654,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
values.put(RECIPIENT_ID, groupRecipientId.serialize())
|
||||
values.put(GROUP_ID, groupId.toString())
|
||||
values.put(TITLE, title)
|
||||
membershipValues.addAll(members.toContentValues(groupId))
|
||||
membershipValues.addAll(members.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
|
||||
values.put(MMS, groupId.isMms)
|
||||
|
||||
if (avatar != null) {
|
||||
|
@ -657,6 +671,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
if (groupId.isV2) {
|
||||
values.put(ACTIVE, if (groupState != null && gv2GroupActive(groupState)) 1 else 0)
|
||||
values.put(DISTRIBUTION_ID, DistributionId.create().toString())
|
||||
values.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements?.expirationMs ?: 0)
|
||||
} else if (groupId.isV1) {
|
||||
values.put(ACTIVE, 1)
|
||||
values.put(EXPECTED_V2_ID, groupId.requireV1().deriveV2MigrationGroupId().toString())
|
||||
|
@ -676,7 +691,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
values.put(V2_REVISION, groupState.revision)
|
||||
values.put(V2_DECRYPTED_GROUP, groupState.encode())
|
||||
membershipValues.clear()
|
||||
membershipValues.addAll(groupMembers.toContentValues(groupId))
|
||||
membershipValues.addAll(groupMembers.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
|
||||
} else {
|
||||
if (groupId.isV2) {
|
||||
throw AssertionError("V2 group id but no master key")
|
||||
|
@ -691,9 +706,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
return false
|
||||
}
|
||||
|
||||
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID), membershipValues)) {
|
||||
for (query in SqlUtil.buildBulkInsert(MembershipTable.TABLE_NAME, arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT), membershipValues)) {
|
||||
writableDatabase.execSQL(query.where, query.whereArgs)
|
||||
}
|
||||
|
||||
writableDatabase.setTransactionSuccessful()
|
||||
} finally {
|
||||
writableDatabase.endTransaction()
|
||||
|
@ -745,11 +761,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
notifyConversationListListeners()
|
||||
}
|
||||
|
||||
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup)
|
||||
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements)
|
||||
}
|
||||
|
||||
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup) {
|
||||
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
val groupRecipientId: RecipientId = recipients.getOrInsertFromGroupId(groupId)
|
||||
val existingGroup: Optional<GroupRecord> = getGroup(groupId)
|
||||
val title: String = decryptedGroup.title
|
||||
|
@ -760,6 +776,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
contentValues.put(V2_DECRYPTED_GROUP, decryptedGroup.encode())
|
||||
contentValues.put(ACTIVE, if (gv2GroupActive(decryptedGroup)) 1 else 0)
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements.expirationMs)
|
||||
}
|
||||
|
||||
if (existingGroup.isPresent && existingGroup.get().unmigratedV1Members.isNotEmpty() && existingGroup.get().isV2Group) {
|
||||
val unmigratedV1Members: MutableSet<RecipientId> = existingGroup.get().unmigratedV1Members.toMutableSet()
|
||||
|
||||
|
@ -781,6 +801,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
}
|
||||
|
||||
val groupMembers = getV2GroupMembers(decryptedGroup, true)
|
||||
var groupSendEndorsementRecords: GroupSendEndorsementRecords? = receivedGroupSendEndorsements?.toGroupSendEndorsementRecords() ?: getGroupSendEndorsements(groupId)
|
||||
|
||||
val addedMembers: List<RecipientId> = if (existingGroup.isPresent && existingGroup.get().isV2Group) {
|
||||
val change = GroupChangeReconstruct.reconstructGroupChange(existingGroup.get().requireV2GroupProperties().decryptedGroup, decryptedGroup)
|
||||
|
@ -800,6 +821,12 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
)
|
||||
}
|
||||
|
||||
if (receivedGroupSendEndorsements == null && (removed.isNotEmpty() || change.newMembers.isNotEmpty())) {
|
||||
Log.v(TAG, "Members were removed or added, and no new endorsements, clearing endorsements and GSE expiration")
|
||||
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, 0)
|
||||
groupSendEndorsementRecords = null
|
||||
}
|
||||
|
||||
change.newMembers.toAciList().toRecipientIds()
|
||||
} else {
|
||||
groupMembers
|
||||
|
@ -812,7 +839,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
.where("$GROUP_ID = ?", groupId.toString())
|
||||
.run()
|
||||
|
||||
performMembershipUpdate(database, groupId, groupMembers)
|
||||
performMembershipUpdate(database, groupId, groupMembers, groupSendEndorsementRecords)
|
||||
}
|
||||
|
||||
if (decryptedGroup.disappearingMessagesTimer != null) {
|
||||
|
@ -867,7 +894,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
.toMutableList()
|
||||
}
|
||||
|
||||
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>) {
|
||||
private fun performMembershipUpdate(database: SQLiteDatabase, groupId: GroupId, members: Collection<RecipientId>, groupSendEndorsementRecords: GroupSendEndorsementRecords?) {
|
||||
check(database.inTransaction())
|
||||
database
|
||||
.delete(MembershipTable.TABLE_NAME)
|
||||
|
@ -876,8 +903,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
|
||||
val inserts = SqlUtil.buildBulkInsert(
|
||||
MembershipTable.TABLE_NAME,
|
||||
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID),
|
||||
members.toSet().toContentValues(groupId)
|
||||
arrayOf(MembershipTable.GROUP_ID, MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT),
|
||||
members.toSet().toContentValues(groupId, groupSendEndorsementRecords)
|
||||
)
|
||||
|
||||
inserts.forEach {
|
||||
|
@ -906,6 +933,94 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
.run()
|
||||
}
|
||||
|
||||
fun getGroupSendEndorsementsExpiration(groupId: GroupId): Long {
|
||||
return writableDatabase
|
||||
.select(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$GROUP_ID = ?", groupId)
|
||||
.run()
|
||||
.readToSingleLong(0L)
|
||||
}
|
||||
|
||||
fun updateGroupSendEndorsements(groupId: GroupId.V2, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements) {
|
||||
val endorsements: Map<RecipientId, GroupSendEndorsement?> = receivedGroupSendEndorsements.toGroupSendEndorsementRecords().endorsements
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(MembershipTable.TABLE_NAME, contentValuesOf(MembershipTable.ENDORSEMENT to null), "${MembershipTable.GROUP_ID} = ?", arrayOf(groupId.serialize()))
|
||||
|
||||
for ((recipientId, endorsement) in endorsements) {
|
||||
db.update(MembershipTable.TABLE_NAME)
|
||||
.values(MembershipTable.ENDORSEMENT to endorsement?.serialize())
|
||||
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
|
||||
.run()
|
||||
}
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(GROUP_SEND_ENDORSEMENTS_EXPIRATION to receivedGroupSendEndorsements.expirationMs)
|
||||
.where("$GROUP_ID = ?", groupId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun getGroupSendEndorsements(groupId: GroupId): GroupSendEndorsementRecords {
|
||||
val allEndorsements: Map<RecipientId, GroupSendEndorsement?> = readableDatabase
|
||||
.select(MembershipTable.RECIPIENT_ID, MembershipTable.ENDORSEMENT)
|
||||
.from(MembershipTable.TABLE_NAME)
|
||||
.where("${MembershipTable.GROUP_ID} = ?", groupId)
|
||||
.run()
|
||||
.readToMap { cursor ->
|
||||
val recipientId = RecipientId.from(cursor.requireLong(MembershipTable.RECIPIENT_ID))
|
||||
val endorsement = cursor.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
|
||||
try {
|
||||
GroupSendEndorsement(endorsementBlob)
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
recipientId to endorsement
|
||||
}
|
||||
|
||||
return GroupSendEndorsementRecords(allEndorsements)
|
||||
}
|
||||
|
||||
fun getGroupSendFullToken(threadId: Long, recipientId: RecipientId): GroupSendFullToken? {
|
||||
val threadRecipient = SignalDatabase.threads.getRecipientForThreadId(threadId)
|
||||
if (threadRecipient == null || !threadRecipient.isGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
return getGroupSendFullToken(threadRecipient.requireGroupId().requireV2(), recipientId)
|
||||
}
|
||||
|
||||
fun getGroupSendFullToken(groupId: GroupId.V2, recipientId: RecipientId): GroupSendFullToken? {
|
||||
val groupRecord = SignalDatabase.groups.getGroup(groupId).orElse(null) ?: return null
|
||||
val endorsement = SignalDatabase.groups.getGroupSendEndorsement(groupId, recipientId) ?: return null
|
||||
|
||||
val groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.requireV2GroupProperties().groupMasterKey)
|
||||
return endorsement.toFullToken(groupSecretParams, Instant.ofEpochMilli(groupRecord.groupSendEndorsementExpiration))
|
||||
}
|
||||
|
||||
private fun getGroupSendEndorsement(groupId: GroupId, recipientId: RecipientId): GroupSendEndorsement? {
|
||||
return readableDatabase
|
||||
.select(MembershipTable.ENDORSEMENT)
|
||||
.from(MembershipTable.TABLE_NAME)
|
||||
.where("${MembershipTable.GROUP_ID} = ? AND ${MembershipTable.RECIPIENT_ID} = ?", groupId, recipientId)
|
||||
.run()
|
||||
.readToSingleObject { c ->
|
||||
c.requireBlob(MembershipTable.ENDORSEMENT)?.let { endorsementBlob ->
|
||||
try {
|
||||
GroupSendEndorsement(endorsementBlob)
|
||||
} catch (e: InvalidInputException) {
|
||||
Log.w(TAG, "Unable to parse group send endorsement for $recipientId", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun isCurrentMember(groupId: Push, recipientId: RecipientId): Boolean {
|
||||
return readableDatabase
|
||||
|
@ -1008,7 +1123,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
groupRevision = cursor.requireInt(V2_REVISION),
|
||||
decryptedGroupBytes = cursor.requireBlob(V2_DECRYPTED_GROUP),
|
||||
distributionId = cursor.optionalString(DISTRIBUTION_ID).map { id -> DistributionId.from(id) }.orElse(null),
|
||||
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP)
|
||||
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP),
|
||||
groupSendEndorsementExpiration = cursor.requireLong(GROUP_SEND_ENDORSEMENTS_EXPIRATION)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1220,15 +1336,20 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
|
|||
return RecipientId.toSerializedList(this)
|
||||
}
|
||||
|
||||
private fun Collection<RecipientId>.toContentValues(groupId: GroupId): List<ContentValues> {
|
||||
private fun Collection<RecipientId>.toContentValues(groupId: GroupId, groupSendEndorsementRecords: GroupSendEndorsementRecords?): List<ContentValues> {
|
||||
return map {
|
||||
contentValuesOf(
|
||||
MembershipTable.GROUP_ID to groupId.serialize(),
|
||||
MembershipTable.RECIPIENT_ID to it.serialize()
|
||||
MembershipTable.RECIPIENT_ID to it.serialize(),
|
||||
MembershipTable.ENDORSEMENT to groupSendEndorsementRecords?.endorsements?.get(it)?.serialize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ReceivedGroupSendEndorsements.toGroupSendEndorsementRecords(): GroupSendEndorsementRecords {
|
||||
return GroupSendEndorsementRecords(endorsements.map { (aci, endorsement) -> RecipientId.from(aci) to endorsement }.toMap())
|
||||
}
|
||||
|
||||
private fun serviceIdsToRecipientIds(serviceIds: Sequence<ServiceId>): MutableList<RecipientId> {
|
||||
return serviceIds
|
||||
.map { serviceId ->
|
||||
|
|
|
@ -958,10 +958,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
|
||||
Log.i(TAG, "Creating restore placeholder for $groupId")
|
||||
val createdId = groups.create(
|
||||
masterKey,
|
||||
DecryptedGroup.Builder()
|
||||
.revision(GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
|
||||
.build()
|
||||
groupMasterKey = masterKey,
|
||||
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
|
||||
if (createdId == null) {
|
||||
|
@ -1469,9 +1468,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
}
|
||||
}
|
||||
|
||||
fun setUnidentifiedAccessMode(id: RecipientId, unidentifiedAccessMode: UnidentifiedAccessMode) {
|
||||
fun setSealedSenderAccessMode(id: RecipientId, sealedSenderAccessMode: SealedSenderAccessMode) {
|
||||
val values = ContentValues(1).apply {
|
||||
put(SEALED_SENDER_MODE, unidentifiedAccessMode.mode)
|
||||
put(SEALED_SENDER_MODE, sealedSenderAccessMode.mode)
|
||||
}
|
||||
if (update(id, values)) {
|
||||
AppDependencies.databaseObserver.notifyRecipientChanged(id)
|
||||
|
@ -1554,7 +1553,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
val valuesToSet = ContentValues(3).apply {
|
||||
put(PROFILE_KEY, encodedProfileKey)
|
||||
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
|
||||
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
|
||||
}
|
||||
|
||||
val updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, valuesToCompare)
|
||||
|
@ -1586,7 +1585,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
val valuesToSet = ContentValues(3).apply {
|
||||
put(PROFILE_KEY, Base64.encodeWithPadding(profileKey.serialize()))
|
||||
putNull(EXPIRING_PROFILE_KEY_CREDENTIAL)
|
||||
put(SEALED_SENDER_MODE, UnidentifiedAccessMode.UNKNOWN.mode)
|
||||
put(SEALED_SENDER_MODE, SealedSenderAccessMode.UNKNOWN.mode)
|
||||
}
|
||||
|
||||
if (writableDatabase.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
|
||||
|
@ -4610,14 +4609,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||
}
|
||||
}
|
||||
|
||||
enum class UnidentifiedAccessMode(val mode: Int) {
|
||||
enum class SealedSenderAccessMode(val mode: Int) {
|
||||
UNKNOWN(0),
|
||||
DISABLED(1),
|
||||
ENABLED(2),
|
||||
UNRESTRICTED(3);
|
||||
|
||||
companion object {
|
||||
fun fromMode(mode: Int): UnidentifiedAccessMode {
|
||||
fun fromMode(mode: Int): SealedSenderAccessMode {
|
||||
return values()[mode]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ object RecipientTableCursorUtil {
|
|||
profileSharing = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
|
||||
lastProfileFetch = cursor.requireLong(RecipientTable.LAST_PROFILE_FETCH),
|
||||
notificationChannel = cursor.requireString(RecipientTable.NOTIFICATION_CHANNEL),
|
||||
unidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
sealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.fromMode(cursor.requireInt(RecipientTable.SEALED_SENDER_MODE)),
|
||||
capabilities = readCapabilities(cursor),
|
||||
storageId = Base64.decodeNullableOrThrow(cursor.requireString(RecipientTable.STORAGE_SERVICE_ID)),
|
||||
mentionSetting = RecipientTable.MentionSetting.fromId(cursor.requireInt(RecipientTable.MENTION_SETTING)),
|
||||
|
|
|
@ -95,6 +95,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V234_ThumbnailResto
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V235_AttachmentUuidColumn
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V236_FixInAppSubscriberCurrencyIfAble
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V237_ResetGroupForceUpdateTimestamps
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V238_AddGroupSendEndorsementsColumns
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
|
@ -192,10 +193,11 @@ object SignalDatabaseMigrations {
|
|||
234 to V234_ThumbnailRestoreStateColumn,
|
||||
235 to V235_AttachmentUuidColumn,
|
||||
236 to V236_FixInAppSubscriberCurrencyIfAble,
|
||||
237 to V237_ResetGroupForceUpdateTimestamps
|
||||
237 to V237_ResetGroupForceUpdateTimestamps,
|
||||
238 to V238_AddGroupSendEndorsementsColumns
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 237
|
||||
const val DATABASE_VERSION = 238
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Add columns to group and group membership tables needed for group send endorsements.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V238_AddGroupSendEndorsementsColumns : SignalDatabaseMigration {
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE groups ADD COLUMN group_send_endorsements_expiration INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE group_membership ADD COLUMN endorsement BLOB DEFAULT NULL")
|
||||
}
|
||||
}
|
|
@ -31,7 +31,8 @@ class GroupRecord(
|
|||
groupRevision: Int,
|
||||
decryptedGroupBytes: ByteArray?,
|
||||
val distributionId: DistributionId?,
|
||||
val lastForceUpdateTimestamp: Long
|
||||
val lastForceUpdateTimestamp: Long,
|
||||
val groupSendEndorsementExpiration: Long
|
||||
) {
|
||||
|
||||
val members: List<RecipientId> by lazy {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
* Contains the individual group send endorsements for a specific group
|
||||
* source from our local db.
|
||||
*/
|
||||
data class GroupSendEndorsementRecords(val endorsements: Map<RecipientId, GroupSendEndorsement?>) {
|
||||
fun getEndorsement(recipientId: RecipientId): GroupSendEndorsement? {
|
||||
return endorsements[recipientId]
|
||||
}
|
||||
|
||||
fun isMissingAnyEndorsements(): Boolean {
|
||||
return endorsements.values.any { it == null }
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.database.RecipientTable
|
|||
import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName
|
||||
|
@ -60,7 +60,7 @@ data class RecipientRecord(
|
|||
val profileSharing: Boolean,
|
||||
val lastProfileFetch: Long,
|
||||
val notificationChannel: String?,
|
||||
val unidentifiedAccessMode: UnidentifiedAccessMode,
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode,
|
||||
val capabilities: Capabilities,
|
||||
val storageId: ByteArray?,
|
||||
val mentionSetting: MentionSetting,
|
||||
|
|
|
@ -172,6 +172,16 @@ public final class GroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void updateGroupSendEndorsements(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey)
|
||||
throws GroupChangeBusyException, IOException, GroupNotAMemberException
|
||||
{
|
||||
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
|
||||
updater.updateGroupSendEndorsements();
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static void setMemberAdmin(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
|||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
|
@ -49,6 +50,8 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
|
||||
|
@ -219,16 +222,18 @@ final class GroupManagerV2 {
|
|||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.generate();
|
||||
DecryptedGroup decryptedGroup;
|
||||
DecryptedGroupResponse createGroupResponse;
|
||||
|
||||
try {
|
||||
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
createGroupResponse = createGroupOnServer(groupSecretParams, name, avatar, members, disappearingMessagesTimer);
|
||||
} catch (GroupAlreadyExistsException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup);
|
||||
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
|
||||
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
|
||||
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
|
||||
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
|
||||
|
||||
if (groupId == null) {
|
||||
throw new GroupChangeFailedException("Unable to create group, group already exists");
|
||||
|
@ -670,7 +675,8 @@ final class GroupManagerV2 {
|
|||
|
||||
previousGroupState = v2GroupProperties.getDecryptedGroup();
|
||||
|
||||
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitToServer(changeActions);
|
||||
GroupChange signedGroupChange = changeResponse.groupChange;
|
||||
try {
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
|
@ -680,7 +686,7 @@ final class GroupManagerV2 {
|
|||
throw new IOException(e);
|
||||
}
|
||||
|
||||
groupDatabase.update(groupId, decryptedGroupState);
|
||||
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.groupSendEndorsementsResponse));
|
||||
|
||||
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);
|
||||
|
@ -690,7 +696,7 @@ final class GroupManagerV2 {
|
|||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
||||
{
|
||||
try {
|
||||
|
@ -747,6 +753,14 @@ final class GroupManagerV2 {
|
|||
.forceSanityUpdateFromServer(timestamp);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void updateGroupSendEndorsements()
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
GroupsV2StateProcessor.forGroup(serviceIds, groupMasterKey)
|
||||
.updateGroupSendEndorsements();
|
||||
}
|
||||
|
||||
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
|
||||
if (signedGroupChange != null && signedGroupChange.length > 0) {
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||
|
@ -764,11 +778,11 @@ final class GroupManagerV2 {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull DecryptedGroup createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
private @NonNull DecryptedGroupResponse createGroupOnServer(@NonNull GroupSecretParams groupSecretParams,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members,
|
||||
int disappearingMessageTimerSeconds)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupAlreadyExistsException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(members)) {
|
||||
|
@ -797,15 +811,10 @@ final class GroupManagerV2 {
|
|||
disappearingMessageTimerSeconds);
|
||||
|
||||
try {
|
||||
groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
DecryptedGroupResponse groupResponse = groupsV2Api.putNewGroup(newGroup, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
|
||||
DecryptedGroup decryptedGroup = groupsV2Api.getGroup(groupSecretParams, AppDependencies.getGroupsV2Authorization().getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
if (decryptedGroup == null) {
|
||||
throw new GroupChangeFailedException();
|
||||
}
|
||||
|
||||
return decryptedGroup;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
return groupResponse;
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (GroupExistsException e) {
|
||||
throw new GroupAlreadyExistsException(e);
|
||||
|
@ -837,8 +846,9 @@ final class GroupManagerV2 {
|
|||
@Nullable byte[] avatar)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException
|
||||
{
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean requestToJoin = joinInfo.addFromInviteLink == AccessControl.AccessRequired.ADMINISTRATOR;
|
||||
boolean alreadyAMember = false;
|
||||
boolean groupAlreadyExists = false;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, "Requesting to join " + groupId);
|
||||
|
@ -846,10 +856,13 @@ final class GroupManagerV2 {
|
|||
Log.i(TAG, "Joining " + groupId);
|
||||
}
|
||||
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
GroupChangeResponse groupChangeResponse = null;
|
||||
GroupChange signedGroupChange = null;
|
||||
DecryptedGroupChange decryptedChange = null;
|
||||
|
||||
try {
|
||||
signedGroupChange = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
groupChangeResponse = joinGroupOnServer(requestToJoin, joinInfo.revision);
|
||||
signedGroupChange = groupChangeResponse.groupChange;
|
||||
|
||||
if (requestToJoin) {
|
||||
Log.i(TAG, String.format("Successfully requested to join %s on server", groupId));
|
||||
|
@ -857,7 +870,7 @@ final class GroupManagerV2 {
|
|||
Log.i(TAG, String.format("Successfully added self to %s on server", groupId));
|
||||
}
|
||||
|
||||
decryptedChange = decryptChange(signedGroupChange);
|
||||
decryptedChange = decryptChange(Objects.requireNonNull(signedGroupChange));
|
||||
} catch (GroupJoinAlreadyAMemberException e) {
|
||||
Log.i(TAG, "Server reports that we are already a member of " + groupId);
|
||||
alreadyAMember = true;
|
||||
|
@ -869,28 +882,37 @@ final class GroupManagerV2 {
|
|||
|
||||
if (group.isPresent()) {
|
||||
Log.i(TAG, "Group already present locally");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
groupAlreadyExists = true;
|
||||
} else {
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup);
|
||||
GroupId.V2 groupId = groupDatabase.create(groupMasterKey, decryptedGroup, null);
|
||||
|
||||
if (groupId != null) {
|
||||
Log.i(TAG, "Created local group with placeholder");
|
||||
} else {
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally, attempting to apply change");
|
||||
if (decryptedChange != null) {
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange.revision, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to apply join change to existing group", e);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Create placeholder failed, group suddenly present locally");
|
||||
groupAlreadyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupAlreadyExists) {
|
||||
Log.i(TAG, "Attempting to update local group with change/server");
|
||||
try {
|
||||
GroupsV2StateProcessor.forGroup(SignalStore.account().getServiceIds(), groupMasterKey)
|
||||
.updateLocalGroupToRevision(decryptedChange != null ? decryptedChange.revision : GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), decryptedChange);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Despite adding self to group, change/server says we are not a member, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw new GroupChangeFailedException(e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Group data fetch failed, scheduling refresh of group info " + groupId, e);
|
||||
|
||||
AppDependencies.getJobManager()
|
||||
.add(new RequestGroupV2InfoJob(groupId));
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1006,7 +1028,7 @@ final class GroupManagerV2 {
|
|||
return group.build();
|
||||
}
|
||||
|
||||
private @NonNull GroupChange joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
private @NonNull GroupChangeResponse joinGroupOnServer(boolean requestToJoin, int currentRevision)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
if (!GroupsV2CapabilityChecker.allAndSelfHaveServiceId(Collections.singleton(Recipient.self().getId()))) {
|
||||
|
@ -1029,7 +1051,7 @@ final class GroupManagerV2 {
|
|||
return commitJoinChangeWithConflictResolution(currentRevision, change);
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
private @NonNull GroupChangeResponse commitJoinChangeWithConflictResolution(int currentRevision, @NonNull GroupChange.Actions.Builder change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException, GroupJoinAlreadyAMemberException
|
||||
{
|
||||
for (int attempt = 0; attempt < 5; attempt++) {
|
||||
|
@ -1038,10 +1060,10 @@ final class GroupManagerV2 {
|
|||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to join group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChangeResponse changeResponse = commitJoinToServer(changeActions);
|
||||
|
||||
Log.i(TAG, "Successfully joined group at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
return changeResponse;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
Log.w(TAG, "Patch not accepted", e);
|
||||
|
||||
|
@ -1051,7 +1073,7 @@ final class GroupManagerV2 {
|
|||
} else {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
} catch (VerificationFailedException | InvalidGroupStateException ex) {
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidInputException ex) {
|
||||
throw new GroupChangeFailedException(ex);
|
||||
}
|
||||
} catch (ConflictException e) {
|
||||
|
@ -1064,7 +1086,7 @@ final class GroupManagerV2 {
|
|||
throw new GroupChangeFailedException("Unable to join group after conflicts");
|
||||
}
|
||||
|
||||
private @NonNull GroupChange commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
private @NonNull GroupChangeResponse commitJoinToServer(@NonNull GroupChange.Actions change)
|
||||
throws GroupChangeFailedException, IOException, GroupLinkNotActiveException
|
||||
{
|
||||
try {
|
||||
|
@ -1109,7 +1131,7 @@ final class GroupManagerV2 {
|
|||
}
|
||||
|
||||
private boolean testGroupMembership()
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException, InvalidInputException
|
||||
{
|
||||
try {
|
||||
groupsV2Api.getGroup(groupSecretParams, authorization.getAuthorizationForToday(serviceIds, groupSecretParams));
|
||||
|
@ -1140,7 +1162,7 @@ final class GroupManagerV2 {
|
|||
DecryptedGroupChange decryptedChange = groupOperations.decryptChange(signedGroupChange, false).get();
|
||||
DecryptedGroup newGroup = DecryptedGroupUtil.applyWithoutRevisionCheck(decryptedGroup, decryptedChange);
|
||||
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision));
|
||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.revision), null);
|
||||
|
||||
sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange, false);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||
|
@ -1165,7 +1187,7 @@ final class GroupManagerV2 {
|
|||
.build();
|
||||
|
||||
Log.i(TAG, "Trying to cancel request group at V" + changeActions.revision);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions);
|
||||
GroupChange signedGroupChange = commitJoinToServer(changeActions).groupChange;
|
||||
|
||||
Log.i(TAG, "Successfully cancelled group join at V" + changeActions.revision);
|
||||
return signedGroupChange;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.groups.v2.processing
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
||||
|
@ -9,10 +10,15 @@ import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog
|
|||
*/
|
||||
class GroupStateDiff(
|
||||
val previousGroupState: DecryptedGroup?,
|
||||
val serverHistory: List<DecryptedGroupChangeLog>
|
||||
val serverHistory: List<DecryptedGroupChangeLog>,
|
||||
val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?
|
||||
) {
|
||||
|
||||
constructor(previousGroupState: DecryptedGroup?, changedGroupState: DecryptedGroup?, change: DecryptedGroupChange?) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)))
|
||||
constructor(
|
||||
previousGroupState: DecryptedGroup?,
|
||||
changedGroupState: DecryptedGroup?,
|
||||
change: DecryptedGroupChange?
|
||||
) : this(previousGroupState, listOf(DecryptedGroupChangeLog(changedGroupState, change)), null)
|
||||
|
||||
val earliestRevisionNumber: Int
|
||||
get() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.annotation.VisibleForTesting
|
|||
import androidx.annotation.WorkerThread
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
|
@ -43,6 +44,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct
|
|||
import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage
|
||||
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
|
@ -95,8 +97,9 @@ class GroupsV2StateProcessor private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.groupsV2Api
|
||||
private val groupsApi = AppDependencies.signalServiceAccountManager.getGroupsV2Api()
|
||||
private val groupsV2Authorization = AppDependencies.groupsV2Authorization
|
||||
private val groupOperations = AppDependencies.groupsV2Operations.forGroup(groupSecretParams)
|
||||
private val groupId = GroupId.v2(groupSecretParams.getPublicParams().getGroupIdentifier())
|
||||
private val profileAndMessageHelper = ProfileAndMessageHelper.create(serviceIds.aci, groupMasterKey, groupId)
|
||||
|
||||
|
@ -124,6 +127,36 @@ class GroupsV2StateProcessor private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and save the latest group send endorsements from the server. This endorsements returned may
|
||||
* not match our local view of the membership if the membership has changed on the server and we haven't updated the
|
||||
* group state yet. This is only an issue when trying to send to a group member that has been removed and should be handled
|
||||
* gracefully as a fallback in the sending flow.
|
||||
*/
|
||||
@WorkerThread
|
||||
@Throws(IOException::class, GroupNotAMemberException::class)
|
||||
fun updateGroupSendEndorsements() {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val groupResponse = when (result) {
|
||||
is NetworkResult.Success -> result.result
|
||||
else -> when (val cause = result.getCause()!!) {
|
||||
is NotInGroupException, is GroupNotFoundException -> throw GroupNotAMemberException(cause)
|
||||
is IOException -> throw cause
|
||||
else -> throw IOException(cause)
|
||||
}
|
||||
}
|
||||
|
||||
val receivedGroupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
|
||||
if (receivedGroupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating group send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, receivedGroupSendEndorsements)
|
||||
} else {
|
||||
Log.w(TAG, "$logPrefix No group send endorsements on response")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Using network where required, will attempt to bring the local copy of the group up to the revision specified.
|
||||
*
|
||||
|
@ -233,7 +266,8 @@ class GroupsV2StateProcessor private constructor(
|
|||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = serverGuid,
|
||||
groupStateDiff = groupStateDiff
|
||||
groupStateDiff = groupStateDiff,
|
||||
groupSendEndorsements = null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -259,6 +293,8 @@ class GroupsV2StateProcessor private constructor(
|
|||
else -> return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val sendEndorsementExpiration = groupRecord.map { it.groupSendEndorsementExpiration }.orElse(0L)
|
||||
|
||||
var includeFirstState = currentLocalState == null ||
|
||||
currentLocalState.revision < 0 ||
|
||||
currentLocalState.revision == joinedAtRevision ||
|
||||
|
@ -273,20 +309,30 @@ class GroupsV2StateProcessor private constructor(
|
|||
var hasRemainingRemoteChanges = false
|
||||
|
||||
while (hasMore) {
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState")
|
||||
Log.i(TAG, "$logPrefix Requesting change logs from server, currentRevision=${currentLocalState?.revision ?: "null"} logsNeededFrom=$logsNeededFrom includeFirstState=$includeFirstState sendEndorsementExpiration=${sendEndorsementExpiration > 0}")
|
||||
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState)
|
||||
val (remoteGroupStateDiff, pagingData) = getGroupChangeLogs(currentLocalState, logsNeededFrom, includeFirstState, sendEndorsementExpiration)
|
||||
val applyGroupStateDiffResult: AdvanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(remoteGroupStateDiff, targetRevision)
|
||||
val updatedGroupState: DecryptedGroup? = applyGroupStateDiffResult.updatedGroupState
|
||||
|
||||
if (updatedGroupState == null || updatedGroupState == remoteGroupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state is at or later than server revision: ${currentLocalState?.revision ?: "null"}")
|
||||
|
||||
if (currentLocalState != null) {
|
||||
val endorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, currentLocalState, remoteGroupStateDiff.groupSendEndorsementsResponse)
|
||||
|
||||
if (endorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Received updated send endorsements, saving")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, endorsements)
|
||||
}
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
}
|
||||
|
||||
Log.i(TAG, "$logPrefix Saving updated group state at revision: ${updatedGroupState.revision}")
|
||||
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState)
|
||||
saveGroupState(remoteGroupStateDiff, updatedGroupState, groupOperations.receiveGroupSendEndorsements(serviceIds.aci, updatedGroupState, remoteGroupStateDiff.groupSendEndorsementsResponse))
|
||||
|
||||
if (addMessagesForAllUpdates) {
|
||||
Log.d(TAG, "$logPrefix Inserting group changes into chat history")
|
||||
|
@ -320,7 +366,7 @@ class GroupsV2StateProcessor private constructor(
|
|||
}
|
||||
|
||||
if (!addMessagesForAllUpdates) {
|
||||
Log.i(TAG, "Inserting single update message for restore placeholder")
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for restore placeholder")
|
||||
profileAndMessageHelper.insertUpdateMessages(runningTimestamp, null, setOf(AppliedGroupChangeLog(currentLocalState!!, null)), serverGuid)
|
||||
}
|
||||
|
||||
|
@ -341,19 +387,20 @@ class GroupsV2StateProcessor private constructor(
|
|||
private fun updateToLatestViaServer(timestamp: Long, currentLocalState: DecryptedGroup?, reconstructChange: Boolean): InternalUpdateResult {
|
||||
val result = groupsApi.getGroupAsResult(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams))
|
||||
|
||||
val serverState = if (result is NetworkResult.Success) {
|
||||
val groupResponse = if (result is NetworkResult.Success) {
|
||||
result.result
|
||||
} else {
|
||||
return InternalUpdateResult.from(result.getCause()!!)
|
||||
}
|
||||
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, serverState) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, serverState, completeGroupChange)
|
||||
val completeGroupChange = if (reconstructChange) GroupChangeReconstruct.reconstructGroupChange(currentLocalState, groupResponse.group) else null
|
||||
val remoteGroupStateDiff = GroupStateDiff(currentLocalState, groupResponse.group, completeGroupChange)
|
||||
|
||||
return saveGroupUpdate(
|
||||
timestamp = timestamp,
|
||||
serverGuid = null,
|
||||
groupStateDiff = remoteGroupStateDiff
|
||||
groupStateDiff = remoteGroupStateDiff,
|
||||
groupSendEndorsements = groupOperations.receiveGroupSendEndorsements(serviceIds.aci, groupResponse.group, groupResponse.groupSendEndorsementsResponse)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -403,22 +450,30 @@ class GroupsV2StateProcessor private constructor(
|
|||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun getGroupChangeLogs(localState: DecryptedGroup?, logsNeededFromRevision: Int, includeFirstState: Boolean): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
private fun getGroupChangeLogs(
|
||||
localState: DecryptedGroup?,
|
||||
logsNeededFromRevision: Int,
|
||||
includeFirstState: Boolean,
|
||||
sendEndorsementsExpirationMs: Long
|
||||
): Pair<GroupStateDiff, GroupHistoryPage.PagingData> {
|
||||
try {
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState)
|
||||
val groupHistoryPage = groupsApi.getGroupHistoryPage(groupSecretParams, logsNeededFromRevision, groupsV2Authorization.getAuthorizationForToday(serviceIds, groupSecretParams), includeFirstState, sendEndorsementsExpirationMs)
|
||||
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs) to groupHistoryPage.pagingData
|
||||
return GroupStateDiff(localState, groupHistoryPage.changeLogs, groupHistoryPage.groupSendEndorsementsResponse) to groupHistoryPage.pagingData
|
||||
} catch (e: InvalidGroupStateException) {
|
||||
throw IOException(e)
|
||||
} catch (e: VerificationFailedException) {
|
||||
throw IOException(e)
|
||||
} catch (e: InvalidInputException) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveGroupUpdate(
|
||||
timestamp: Long,
|
||||
serverGuid: String?,
|
||||
groupStateDiff: GroupStateDiff
|
||||
groupStateDiff: GroupStateDiff,
|
||||
groupSendEndorsements: ReceivedGroupSendEndorsements?
|
||||
): InternalUpdateResult {
|
||||
val currentLocalState: DecryptedGroup? = groupStateDiff.previousGroupState
|
||||
val applyGroupStateDiffResult = GroupStatePatcher.applyGroupStateDiff(groupStateDiff, GroupStatePatcher.LATEST)
|
||||
|
@ -426,12 +481,18 @@ class GroupsV2StateProcessor private constructor(
|
|||
|
||||
if (updatedGroupState == null || updatedGroupState == groupStateDiff.previousGroupState) {
|
||||
Log.i(TAG, "$logPrefix Local state and server state are equal")
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Saving new send endorsements")
|
||||
SignalDatabase.groups.updateGroupSendEndorsements(groupId, groupSendEndorsements)
|
||||
}
|
||||
|
||||
return InternalUpdateResult.NoUpdateNeeded
|
||||
} else {
|
||||
Log.i(TAG, "$logPrefix Local state (revision: ${currentLocalState?.revision}) does not match, updating to ${updatedGroupState.revision}")
|
||||
}
|
||||
|
||||
saveGroupState(groupStateDiff, updatedGroupState)
|
||||
saveGroupState(groupStateDiff, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (currentLocalState == null || currentLocalState.revision == RESTORE_PLACEHOLDER_REVISION) {
|
||||
Log.i(TAG, "$logPrefix Inserting single update message for no local state or restore placeholder")
|
||||
|
@ -453,20 +514,24 @@ class GroupsV2StateProcessor private constructor(
|
|||
return InternalUpdateResult.Updated(updatedGroupState)
|
||||
}
|
||||
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup) {
|
||||
private fun saveGroupState(groupStateDiff: GroupStateDiff, updatedGroupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?) {
|
||||
val previousGroupState = groupStateDiff.previousGroupState
|
||||
|
||||
if (groupSendEndorsements != null) {
|
||||
Log.i(TAG, "$logPrefix Updating send endorsements")
|
||||
}
|
||||
|
||||
val needsAvatarFetch = if (previousGroupState == null) {
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState)
|
||||
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
if (groupId == null) {
|
||||
Log.w(TAG, "$logPrefix Group create failed, trying to update")
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
}
|
||||
|
||||
updatedGroupState.avatar.isNotEmpty()
|
||||
} else {
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState)
|
||||
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements)
|
||||
|
||||
updatedGroupState.avatar != previousGroupState.avatar
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
|
||||
|
@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -136,12 +134,11 @@ public class AutomaticSessionResetJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
try {
|
||||
messageSender.sendNullMessage(address, unidentifiedAccess);
|
||||
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send null message.");
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
|||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
|
@ -83,7 +82,7 @@ class CallLinkUpdateSendJob private constructor(
|
|||
)
|
||||
|
||||
AppDependencies.signalServiceMessageSender
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate), Optional.empty())
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate))
|
||||
|
||||
if (callLinkUpdateType == CallLinkUpdate.Type.DELETE) {
|
||||
SignalDatabase.callLinks.deleteCallLink(callLinkRoomId)
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
|||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
|
@ -98,10 +97,7 @@ class CallLogEventSendJob private constructor(
|
|||
|
||||
override fun onRun() {
|
||||
AppDependencies.signalServiceMessageSender
|
||||
.sendSyncMessage(
|
||||
SignalServiceSyncMessage.forCallLogEvent(callLogEvent),
|
||||
Optional.empty()
|
||||
)
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forCallLogEvent(callLogEvent))
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
|
|
|
@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer
|
|||
import org.thoughtcrime.securesms.service.webrtc.CallEventSyncMessageUtil
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
|
@ -142,7 +141,7 @@ class CallSyncEventJob private constructor(
|
|||
val syncMessage = createSyncMessage(syncTimestamp, callSyncEvent, call.type)
|
||||
|
||||
return try {
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage), Optional.empty())
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(SignalServiceSyncMessage.forCallEvent(syncMessage))
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to send call event sync message for ${callSyncEvent.callId}", e)
|
||||
|
|
|
@ -10,11 +10,11 @@ import com.annimon.stream.Stream;
|
|||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
|
@ -37,7 +37,6 @@ import org.thoughtcrime.securesms.util.Util;
|
|||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
|
@ -160,7 +159,7 @@ public class IndividualSendJob extends PushSendJob {
|
|||
|
||||
Recipient recipient = message.getThreadRecipient().fresh();
|
||||
byte[] profileKey = recipient.getProfileKey();
|
||||
UnidentifiedAccessMode accessMode = recipient.getUnidentifiedAccessMode();
|
||||
SealedSenderAccessMode accessMode = recipient.getSealedSenderAccessMode();
|
||||
|
||||
boolean unidentified = deliver(message, originalEditedMessage);
|
||||
|
||||
|
@ -177,15 +176,15 @@ public class IndividualSendJob extends PushSendJob {
|
|||
SignalDatabase.messages().incrementViewedReceiptCount(message.getSentTimeMillis(), recipient.getId(), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN && profileKey == null) {
|
||||
if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN && profileKey == null) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-unrestricted following a UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.UNRESTRICTED);
|
||||
} else if (unidentified && accessMode == UnidentifiedAccessMode.UNKNOWN) {
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.UNRESTRICTED);
|
||||
} else if (unidentified && accessMode == SealedSenderAccessMode.UNKNOWN) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-enabled following a UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.ENABLED);
|
||||
} else if (!unidentified && accessMode != UnidentifiedAccessMode.DISABLED) {
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.ENABLED);
|
||||
} else if (!unidentified && accessMode != SealedSenderAccessMode.DISABLED) {
|
||||
log(TAG, String.valueOf(message.getSentTimeMillis()), "Marking recipient as UD-disabled following a non-UD send.");
|
||||
SignalDatabase.recipients().setUnidentifiedAccessMode(recipient.getId(), UnidentifiedAccessMode.DISABLED);
|
||||
SignalDatabase.recipients().setSealedSenderAccessMode(recipient.getId(), SealedSenderAccessMode.DISABLED);
|
||||
}
|
||||
|
||||
if (originalEditedMessage != null && originalEditedMessage.getExpireStarted() > 0) {
|
||||
|
@ -306,25 +305,35 @@ public class IndividualSendJob extends PushSendJob {
|
|||
|
||||
if (originalEditedMessage != null) {
|
||||
if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
|
||||
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
|
||||
SendMessageResult result = messageSender.sendSelfSyncEditMessage(new SignalServiceEditMessage(originalEditedMessage.getDateSent(), mediaMessage));
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
|
||||
return syncAccess.isPresent();
|
||||
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
|
||||
} else {
|
||||
SendMessageResult result = messageSender.sendEditMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, IndividualSendEvents.EMPTY, message.isUrgent(), originalEditedMessage.getDateSent());
|
||||
SendMessageResult result = messageSender.sendEditMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
|
||||
ContentHint.RESENDABLE,
|
||||
mediaMessage,
|
||||
IndividualSendEvents.EMPTY,
|
||||
message.isUrgent(),
|
||||
originalEditedMessage.getDateSent());
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
|
||||
return result.getSuccess().isUnidentified();
|
||||
}
|
||||
} else if (Util.equals(SignalStore.account().getAci(), address.getServiceId())) {
|
||||
Optional<UnidentifiedAccessPair> syncAccess = UnidentifiedAccessUtil.getAccessForSync(context);
|
||||
SendMessageResult result = messageSender.sendSyncMessage(mediaMessage);
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), false);
|
||||
return syncAccess.isPresent();
|
||||
return SealedSenderAccessUtil.getSealedSenderCertificate() != null;
|
||||
} else {
|
||||
SignalLocalMetrics.IndividualMessageSend.onDeliveryStarted(messageId, message.getSentTimeMillis());
|
||||
SendMessageResult result = messageSender.sendDataMessage(address, UnidentifiedAccessUtil.getAccessFor(context, messageRecipient), ContentHint.RESENDABLE, mediaMessage, new MetricEventListener(messageId), message.isUrgent(), messageRecipient.getNeedsPniSignature());
|
||||
SendMessageResult result = messageSender.sendDataMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(messageRecipient),
|
||||
ContentHint.RESENDABLE,
|
||||
mediaMessage,
|
||||
new MetricEventListener(messageId),
|
||||
message.isUrgent(),
|
||||
messageRecipient.getNeedsPniSignature());
|
||||
|
||||
SignalDatabase.messageLog().insertIfPossible(messageRecipient.getId(), message.getSentTimeMillis(), result, ContentHint.RESENDABLE, new MessageId(messageId), message.isUrgent());
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RecipientReader;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
|
@ -88,8 +87,8 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(blockedIndividuals, blockedGroups))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
|
|||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate
|
||||
import java.util.Optional
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
|
@ -57,7 +56,7 @@ class MultiDeviceCallLinkSyncJob private constructor(
|
|||
val syncMessage = SignalServiceSyncMessage.forCallLinkUpdate(callLinkUpdate)
|
||||
|
||||
try {
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage, Optional.empty())
|
||||
AppDependencies.signalServiceMessageSender.sendSyncMessage(syncMessage)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to send call link update message.", e)
|
||||
throw e
|
||||
|
|
|
@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
|
@ -106,8 +105,8 @@ public class MultiDeviceConfigurationUpdateJob extends BaseJob {
|
|||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.of(readReceiptsEnabled),
|
||||
Optional.of(unidentifiedDeliveryIndicatorsEnabled),
|
||||
Optional.of(typingIndicatorsEnabled),
|
||||
Optional.of(linkPreviewsEnabled))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
Optional.of(linkPreviewsEnabled)))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,13 +15,12 @@ import org.signal.libsignal.protocol.IdentityKey;
|
|||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
|
@ -298,8 +297,8 @@ public class MultiDeviceContactUpdateJob extends BaseJob {
|
|||
.withLength(length)
|
||||
.withResumableUploadSpec(messageSender.getResumableUploadSpec());
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream.build(), complete))
|
||||
);
|
||||
} catch (IOException ioe) {
|
||||
throw new NetworkException(ioe);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -73,8 +72,8 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
|
|||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey()))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.ofNullable(storageServiceKey), Optional.of(SignalStore.svr().getOrCreateMasterKey())))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,10 +5,9 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -122,8 +121,8 @@ public class MultiDeviceMessageRequestResponseJob extends BaseJob {
|
|||
}
|
||||
|
||||
if (response != null) {
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response)
|
||||
);
|
||||
} else {
|
||||
Log.w(TAG, recipient.getId() + " not registered!");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
|
@ -117,8 +116,8 @@ public final class MultiDeviceOutgoingPaymentSyncJob extends BaseJob {
|
|||
|
||||
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
.sendSyncMessage(SignalServiceSyncMessage.forOutgoingPayment(outgoingPaymentMessage)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -58,8 +57,8 @@ public class MultiDeviceProfileContentUpdateJob extends BaseJob {
|
|||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -98,7 +97,7 @@ public class MultiDeviceProfileKeyUpdateJob extends BaseJob {
|
|||
|
||||
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, false));
|
||||
|
||||
messageSender.sendSyncMessage(syncMessage, UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(syncMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -121,7 +120,7 @@ public class MultiDeviceReadUpdateJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.jobs;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
|
@ -93,8 +92,8 @@ public class MultiDeviceStickerPackOperationJob extends BaseJob {
|
|||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
StickerPackOperationMessage stickerPackOperation = new StickerPackOperationMessage(packIdBytes, packKeyBytes, remoteType);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation)),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(Collections.singletonList(stickerPackOperation))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.jobs;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.Hex;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader;
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord;
|
||||
|
@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
|||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.signal.core.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
|
@ -80,8 +79,8 @@ public class MultiDeviceStickerPackSyncJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(operations)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
|
@ -58,8 +57,7 @@ public class MultiDeviceStorageSyncRequestJob extends BaseJob {
|
|||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessageRe
|
|||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.lang.Exception
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -57,7 +56,7 @@ class MultiDeviceStorySendSyncJob private constructor(parameters: Parameters, pr
|
|||
val updateManifest = SignalDatabase.storySends.getLocalManifest(sentTimestamp)
|
||||
val recipientsSet: Set<SignalServiceStoryMessageRecipient> = updateManifest.toRecipientsSet()
|
||||
val transcriptMessage: SignalServiceSyncMessage = SignalServiceSyncMessage.forSentTranscript(buildSentTranscript(recipientsSet))
|
||||
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage, Optional.empty())
|
||||
val sendMessageResult = AppDependencies.signalServiceMessageSender.sendSyncMessage(transcriptMessage)
|
||||
|
||||
Log.i(TAG, "Sent transcript message with ${recipientsSet.size} recipients")
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
|
@ -57,10 +56,7 @@ class MultiDeviceSubscriptionSyncRequestJob private constructor(parameters: Para
|
|||
|
||||
val messageSender = AppDependencies.signalServiceMessageSender
|
||||
|
||||
messageSender.sendSyncMessage(
|
||||
SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context)
|
||||
)
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.SUBSCRIPTION_STATUS))
|
||||
}
|
||||
|
||||
override fun onShouldRetry(e: Exception): Boolean {
|
||||
|
|
|
@ -4,20 +4,19 @@ package org.thoughtcrime.securesms.jobs;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.IdentityTable.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
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.signal.core.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
|
@ -116,8 +115,8 @@ public class MultiDeviceVerifiedUpdateJob extends BaseJob {
|
|||
SignalServiceAddress verifiedAddress = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
VerifiedMessage verifiedMessage = new VerifiedMessage(verifiedAddress, new IdentityKey(identityKey, 0), verifiedState, timestamp);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)
|
||||
);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@ import androidx.annotation.Nullable;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -92,7 +91,7 @@ public class MultiDeviceViewOnceOpenJob extends BaseJob {
|
|||
|
||||
ViewOnceOpenMessage openMessage = new ViewOnceOpenMessage(RecipientUtil.getOrFetchServiceId(context, recipient), messageId.timestamp);
|
||||
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewOnceOpen(openMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,12 +8,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -121,7 +120,7 @@ public class MultiDeviceViewedUpdateJob extends BaseJob {
|
|||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
messageSender.sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,21 +4,19 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
|
@ -72,12 +70,11 @@ public class NullMessageSendJob extends BaseJob {
|
|||
Log.w(TAG, recipient.getId() + " not registered!");
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
try {
|
||||
messageSender.sendNullMessage(address, unidentifiedAccess);
|
||||
messageSender.sendNullMessage(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send null message.");
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.PaymentTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
@ -18,14 +18,12 @@ import org.thoughtcrime.securesms.transport.RetryLaterException;
|
|||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.IndividualSendEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PaymentNotificationSendJob extends BaseJob {
|
||||
|
@ -81,9 +79,8 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
|
||||
PaymentTable.PaymentTransaction payment = paymentDatabase.getPayment(uuid);
|
||||
|
||||
|
@ -101,7 +98,13 @@ public final class PaymentNotificationSendJob extends BaseJob {
|
|||
.withPayment(new SignalServiceDataMessage.Payment(new SignalServiceDataMessage.PaymentNotification(payment.getReceipt(), payment.getNote()), null))
|
||||
.build();
|
||||
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address, unidentifiedAccess, ContentHint.DEFAULT, dataMessage, IndividualSendEvents.EMPTY, false, recipient.getNeedsPniSignature());
|
||||
SendMessageResult sendMessageResult = messageSender.sendDataMessage(address,
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
ContentHint.DEFAULT,
|
||||
dataMessage,
|
||||
IndividualSendEvents.EMPTY,
|
||||
false,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
if (recipient.getNeedsPniSignature()) {
|
||||
SignalDatabase.pendingPniSignatureMessages().insertIfNecessary(recipientId, dataMessage.getTimestamp(), sendMessageResult);
|
||||
|
|
|
@ -7,7 +7,7 @@ import org.signal.core.util.ThreadUtil;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
|
@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
@ -141,9 +141,9 @@ public class ResendMessageJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
Content contentToSend = content;
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Content contentToSend = content;
|
||||
SealedSenderAccess.CreateGroupSendToken createGroupSendToken = null;
|
||||
|
||||
if (distributionId != null) {
|
||||
if (groupId != null) {
|
||||
|
@ -157,6 +157,8 @@ public class ResendMessageJob extends BaseJob {
|
|||
Log.w(TAG, "The target user is no longer in the group! Skipping message send.");
|
||||
return;
|
||||
}
|
||||
|
||||
createGroupSendToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, recipientId);
|
||||
} else {
|
||||
Log.d(TAG, "GroupId is not present. Assuming this is a message for a distribution list.");
|
||||
DistributionListRecord listRecord = SignalDatabase.distributionLists().getListByDistributionId(distributionId);
|
||||
|
@ -178,6 +180,8 @@ public class ResendMessageJob extends BaseJob {
|
|||
|
||||
SendMessageResult result;
|
||||
|
||||
SealedSenderAccess access = SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, createGroupSendToken);
|
||||
|
||||
try {
|
||||
result = messageSender.resendContent(address, access, sentTimestamp, contentToSend, contentHint, Optional.ofNullable(groupId).map(GroupId::getDecodedId), urgent);
|
||||
} catch (IllegalStateException e) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.GroupTable
|
|||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.Companion.maskCapabilitiesToLong
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
|
@ -222,7 +222,7 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
|||
unrestrictedUnidentifiedAccess = remoteProfile.isUnrestrictedUnidentifiedAccess
|
||||
)
|
||||
|
||||
if (localRecipientRecord.unidentifiedAccessMode != accessMode) {
|
||||
if (localRecipientRecord.sealedSenderAccessMode != accessMode) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -322,8 +322,8 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
|||
val profileKey = ProfileKeyUtil.profileKeyOrNull(recipient.profileKey)
|
||||
val newMode = deriveUnidentifiedAccessMode(profileKey, unidentifiedAccessVerifier, unrestrictedUnidentifiedAccess)
|
||||
|
||||
if (recipient.unidentifiedAccessMode !== newMode) {
|
||||
if (newMode === UnidentifiedAccessMode.UNRESTRICTED) {
|
||||
if (recipient.sealedSenderAccessMode !== newMode) {
|
||||
if (newMode === SealedSenderAccessMode.UNRESTRICTED) {
|
||||
Log.i(TAG, "Marking recipient UD status as unrestricted.")
|
||||
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
|
||||
Log.i(TAG, "Marking recipient UD status as disabled.")
|
||||
|
@ -331,15 +331,15 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
|||
Log.i(TAG, "Marking recipient UD status as " + newMode.name + " after verification.")
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.setUnidentifiedAccessMode(recipient.id, newMode)
|
||||
SignalDatabase.recipients.setSealedSenderAccessMode(recipient.id, newMode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): UnidentifiedAccessMode {
|
||||
private fun deriveUnidentifiedAccessMode(profileKey: ProfileKey?, unidentifiedAccessVerifier: String?, unrestrictedUnidentifiedAccess: Boolean): SealedSenderAccessMode {
|
||||
return if (unrestrictedUnidentifiedAccess && unidentifiedAccessVerifier != null) {
|
||||
UnidentifiedAccessMode.UNRESTRICTED
|
||||
SealedSenderAccessMode.UNRESTRICTED
|
||||
} else if (profileKey == null || unidentifiedAccessVerifier == null) {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
SealedSenderAccessMode.DISABLED
|
||||
} else {
|
||||
val profileCipher = ProfileCipher(profileKey)
|
||||
val verifiedUnidentifiedAccess: Boolean = try {
|
||||
|
@ -350,9 +350,9 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
|
|||
}
|
||||
|
||||
if (verifiedUnidentifiedAccess) {
|
||||
UnidentifiedAccessMode.ENABLED
|
||||
SealedSenderAccessMode.ENABLED
|
||||
} else {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
SealedSenderAccessMode.DISABLED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -123,7 +124,7 @@ public class SendDeliveryReceiptJob extends BaseJob {
|
|||
timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, this::getGroupSendFullToken),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
|
@ -132,6 +133,19 @@ public class SendDeliveryReceiptJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private @Nullable GroupSendFullToken getGroupSendFullToken() {
|
||||
if (messageId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long threadId = SignalDatabase.messages().getThreadIdForMessage(messageId.getId());
|
||||
if (threadId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception e) {
|
||||
if (e instanceof ServerRejectedException) return false;
|
||||
|
|
|
@ -9,14 +9,14 @@ import androidx.annotation.VisibleForTesting;
|
|||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
@ -188,7 +188,8 @@ public class SendReadReceiptJob extends BaseJob {
|
|||
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageSentTimestamps, timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
|
||||
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
|
|
|
@ -4,19 +4,18 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
|
||||
|
@ -87,12 +86,11 @@ public final class SendRetryReceiptJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<UnidentifiedAccessPair> access = UnidentifiedAccessUtil.getAccessFor(context, recipient);
|
||||
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
|
||||
SignalServiceAddress address = RecipientUtil.toSignalServiceAddress(context, recipient);
|
||||
Optional<byte[]> group = groupId.map(GroupId::getDecodedId);
|
||||
|
||||
Log.i(TAG, "Sending retry receipt for " + errorMessage.getTimestamp() + " to " + recipientId + ", device: " + errorMessage.getDeviceId());
|
||||
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, access, group, errorMessage);
|
||||
AppDependencies.getSignalServiceMessageSender().sendRetryReceipt(address, SealedSenderAccessUtil.getSealedSenderAccessFor(recipient), group, errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,15 +8,15 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.signal.core.util.ListUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
|
@ -208,7 +208,8 @@ public class SendViewedReceiptJob extends BaseJob {
|
|||
timestamp);
|
||||
|
||||
SendMessageResult result = messageSender.sendReceipt(remoteAddress,
|
||||
UnidentifiedAccessUtil.getAccessFor(context, Recipient.resolved(recipientId)),
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient,
|
||||
() -> SignalDatabase.groups().getGroupSendFullToken(threadId, recipientId)),
|
||||
receiptMessage,
|
||||
recipient.getNeedsPniSignature());
|
||||
|
||||
|
|
|
@ -6,19 +6,19 @@ import androidx.annotation.Nullable;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.SignalProtocolAddress;
|
||||
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
@ -85,12 +85,14 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
|
|||
return;
|
||||
}
|
||||
|
||||
GroupId.V2 groupId;
|
||||
DistributionId distributionId;
|
||||
GroupId.V2 groupId;
|
||||
DistributionId distributionId;
|
||||
SealedSenderAccess.CreateGroupSendToken createGroupSendFullToken = null;
|
||||
|
||||
if (threadRecipient.isPushV2Group()) {
|
||||
groupId = threadRecipient.requireGroupId().requireV2();
|
||||
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
|
||||
groupId = threadRecipient.requireGroupId().requireV2();
|
||||
distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
|
||||
createGroupSendFullToken = () -> SignalDatabase.groups().getGroupSendFullToken(groupId, targetRecipientId);
|
||||
} else if (threadRecipient.isDistributionList()) {
|
||||
groupId = null;
|
||||
distributionId = SignalDatabase.distributionLists().getDistributionId(threadRecipientId);
|
||||
|
@ -116,10 +118,10 @@ public final class SenderKeyDistributionSendJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
|
||||
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
|
||||
List<Optional<UnidentifiedAccessPair>> access = UnidentifiedAccessUtil.getAccessFor(context, Collections.singletonList(targetRecipient));
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
List<SignalServiceAddress> address = Collections.singletonList(RecipientUtil.toSignalServiceAddress(context, targetRecipient));
|
||||
SenderKeyDistributionMessage message = messageSender.getOrCreateNewGroupSession(distributionId);
|
||||
List<SealedSenderAccess> access = Collections.singletonList(SealedSenderAccessUtil.getSealedSenderAccessFor(targetRecipient, createGroupSendFullToken));
|
||||
|
||||
SendMessageResult result = messageSender.sendSenderKeyDistributionMessage(distributionId, address, access, message, Optional.ofNullable(groupId).map(GroupId::getDecodedId), false, false).get(0);
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
|||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.UnknownStorageIdTable;
|
||||
|
@ -209,7 +208,7 @@ public class StorageSyncJob extends BaseJob {
|
|||
} else {
|
||||
Log.w(TAG, "Failed to decrypt remote storage! Requesting new keys from primary.", e);
|
||||
SignalStore.storageService().clearStorageKeyFromPrimary();
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)), UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forRequest(RequestMessage.forType(SyncMessage.Request.Type.KEYS)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.messages
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
* Internal user only notifier when "bad" things happen with group send endorsement sends.
|
||||
*/
|
||||
object GroupSendEndorsementInternalNotifier : SealedSenderAccess.FallbackListener {
|
||||
|
||||
private const val TAG = "GSENotifier"
|
||||
|
||||
private var lastGroupSendNotify: Duration = 0.milliseconds
|
||||
private var skippedGroupSendNotifies = 0
|
||||
|
||||
private var lastMissingNotify: Duration = 0.milliseconds
|
||||
|
||||
private var lastFallbackNotify: Duration = 0.milliseconds
|
||||
|
||||
@JvmStatic
|
||||
fun init() {
|
||||
if (RemoteConfig.internalUser) {
|
||||
SealedSenderAccess.fallbackListener = this
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccessToTokenFallback() {
|
||||
Log.w(TAG, "Fallback from access key to token", Throwable())
|
||||
postFallbackError(AppDependencies.application)
|
||||
}
|
||||
|
||||
override fun onTokenToAccessFallback(hasAccessKeyFallback: Boolean) {
|
||||
Log.w(TAG, "Fallback from token hasAccessKey=$hasAccessKeyFallback", Throwable())
|
||||
postFallbackError(AppDependencies.application)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postGroupSendFallbackError(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastGroupSendNotify + 5.minutes > now && skippedGroupSendNotifies < 5) {
|
||||
skippedGroupSendNotifies++
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE failed for group send")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastGroupSendNotify = now
|
||||
skippedGroupSendNotifies = 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postMissingGroupSendEndorsement(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastMissingNotify + 5.minutes > now) {
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE missing for recipient")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastMissingNotify = now
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun postFallbackError(context: Context) {
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
if (lastFallbackNotify + 5.minutes > now) {
|
||||
return
|
||||
}
|
||||
|
||||
val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("[Internal-only] GSE fallback occurred!")
|
||||
.setContentText("Please tap to send a debug log")
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable()))
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification)
|
||||
|
||||
lastFallbackNotify = now
|
||||
}
|
||||
}
|
|
@ -7,34 +7,44 @@ import androidx.annotation.Nullable;
|
|||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
|
||||
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.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.MessageSendLogTables;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId;
|
||||
import org.thoughtcrime.securesms.database.model.GroupRecord;
|
||||
import org.thoughtcrime.securesms.database.model.GroupSendEndorsementRecords;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.CancelationException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
|
||||
|
@ -43,6 +53,7 @@ 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.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
|
@ -52,6 +63,7 @@ import org.whispersystems.signalservice.internal.push.http.PartialSendBatchCompl
|
|||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -248,28 +260,81 @@ public final class GroupSendUtil {
|
|||
Set<Recipient> unregisteredTargets = allTargets.stream().filter(Recipient::isUnregistered).collect(Collectors.toSet());
|
||||
List<Recipient> registeredTargets = allTargets.stream().filter(r -> !unregisteredTargets.contains(r)).collect(Collectors.toList());
|
||||
|
||||
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
|
||||
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
|
||||
RecipientData recipients = new RecipientData(context, registeredTargets, isStorySend);
|
||||
Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.empty();
|
||||
GroupSendEndorsementRecords groupSendEndorsementRecords = groupRecord.filter(GroupRecord::isV2Group).map(g -> SignalDatabase.groups().getGroupSendEndorsements(g.getId())).orElse(null);
|
||||
long groupSendEndorsementExpiration = groupRecord.map(GroupRecord::getGroupSendEndorsementExpiration).orElse(0L);
|
||||
SenderCertificate senderCertificate = SealedSenderAccessUtil.getSealedSenderCertificate();
|
||||
boolean useGroupSendEndorsements = groupSendEndorsementRecords != null;
|
||||
|
||||
if (useGroupSendEndorsements && senderCertificate == null) {
|
||||
Log.w(TAG, "Can't use group send endorsements without a sealed sender certificate, falling back to access key");
|
||||
useGroupSendEndorsements = false;
|
||||
} else if (useGroupSendEndorsements) {
|
||||
boolean refreshGroupSendEndorsements = false;
|
||||
|
||||
if (groupSendEndorsementExpiration == 0) {
|
||||
Log.i(TAG, "No group send endorsements expiration set, need to refresh");
|
||||
refreshGroupSendEndorsements = true;
|
||||
} else if (groupSendEndorsementExpiration - TimeUnit.HOURS.toMillis(2) < System.currentTimeMillis()) {
|
||||
Log.i(TAG, "Group send endorsements are expired or expire imminently, refresh. Expires in " + (groupSendEndorsementExpiration - System.currentTimeMillis()) + "ms");
|
||||
refreshGroupSendEndorsements = true;
|
||||
} else if (groupSendEndorsementRecords.isMissingAnyEndorsements()) {
|
||||
Log.i(TAG, "Missing group send endorsements for some members, refresh.");
|
||||
refreshGroupSendEndorsements = true;
|
||||
}
|
||||
|
||||
if (refreshGroupSendEndorsements) {
|
||||
try {
|
||||
GroupManager.updateGroupSendEndorsements(context, groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
|
||||
|
||||
groupSendEndorsementExpiration = SignalDatabase.groups().getGroupSendEndorsementsExpiration(groupId);
|
||||
groupSendEndorsementRecords = SignalDatabase.groups().getGroupSendEndorsements(groupId);
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
if (groupSendEndorsementExpiration == 0) {
|
||||
Log.w(TAG, "Unable to update group send endorsements, falling back to access key", e);
|
||||
useGroupSendEndorsements = false;
|
||||
groupSendEndorsementRecords = new GroupSendEndorsementRecords(Collections.emptyMap());
|
||||
} else {
|
||||
Log.w(TAG, "Unable to update group send endorsements, using what we have", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Refresh all group state because we needed to refresh gse");
|
||||
AppDependencies.getJobManager().add(new RequestGroupV2InfoJob(groupId));
|
||||
}
|
||||
}
|
||||
|
||||
List<Recipient> senderKeyTargets = new LinkedList<>();
|
||||
List<Recipient> legacyTargets = new LinkedList<>();
|
||||
|
||||
for (Recipient recipient : registeredTargets) {
|
||||
Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
|
||||
boolean validMembership = true;
|
||||
Optional<UnidentifiedAccess> access = recipients.getAccessPair(recipient.getId());
|
||||
boolean validMembership = groupId == null || (groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId()));
|
||||
|
||||
if (groupId != null && (!groupRecord.isPresent() || !groupRecord.get().getMembers().contains(recipient.getId()))) {
|
||||
validMembership = false;
|
||||
}
|
||||
|
||||
if (recipient.getHasServiceId() &&
|
||||
access.isPresent() &&
|
||||
access.get().getTargetUnidentifiedAccess().isPresent() &&
|
||||
validMembership)
|
||||
{
|
||||
senderKeyTargets.add(recipient);
|
||||
if (useGroupSendEndorsements) {
|
||||
GroupSendEndorsement groupSendEndorsement = groupSendEndorsementRecords.getEndorsement(recipient.getId());
|
||||
if (groupSendEndorsement != null && recipient.getHasAci() && validMembership) {
|
||||
senderKeyTargets.add(recipient);
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
if (validMembership) {
|
||||
Log.w(TAG, "Should be using group send endorsement but not found for " + recipient.getId());
|
||||
if (RemoteConfig.internalUser()) {
|
||||
GroupSendEndorsementInternalNotifier.postMissingGroupSendEndorsement(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
// Use sender key
|
||||
if (recipient.getHasServiceId() &&
|
||||
access.isPresent() &&
|
||||
validMembership)
|
||||
{
|
||||
senderKeyTargets.add(recipient);
|
||||
} else {
|
||||
legacyTargets.add(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +364,7 @@ public final class GroupSendUtil {
|
|||
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
|
||||
SignalServiceMessageSender messageSender = AppDependencies.getSignalServiceMessageSender();
|
||||
|
||||
if (senderKeyTargets.size() > 0 && distributionId != null) {
|
||||
if (Util.hasItems(senderKeyTargets) && distributionId != null) {
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
|
||||
|
@ -309,14 +374,36 @@ public final class GroupSendUtil {
|
|||
}
|
||||
|
||||
try {
|
||||
List<SignalServiceAddress> targets = senderKeyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<UnidentifiedAccess> access = senderKeyTargets.stream().map(r -> recipients.requireAccess(r.getId())).collect(Collectors.toList());
|
||||
List<SignalServiceAddress> targets = new ArrayList<>(senderKeyTargets.size());
|
||||
List<UnidentifiedAccess> access = new ArrayList<>(senderKeyTargets.size());
|
||||
Map<ServiceId.ACI, GroupSendEndorsement> senderKeyEndorsements = new HashMap<>(senderKeyTargets.size());
|
||||
GroupSendEndorsements groupSendEndorsements = null;
|
||||
|
||||
for (Recipient recipient : senderKeyTargets) {
|
||||
targets.add(recipients.getAddress(recipient.getId()));
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
senderKeyEndorsements.put(recipient.requireAci(), groupSendEndorsementRecords.getEndorsement(recipient.getId()));
|
||||
access.add(recipients.getAccess(recipient.getId()));
|
||||
} else {
|
||||
access.add(recipients.requireAccess(recipient.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
groupSendEndorsements = new GroupSendEndorsements(
|
||||
groupSendEndorsementExpiration,
|
||||
senderKeyEndorsements,
|
||||
senderCertificate,
|
||||
GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey())
|
||||
);
|
||||
}
|
||||
|
||||
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate, partialResults -> {
|
||||
List<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, partialResults -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
|
@ -343,6 +430,10 @@ public final class GroupSendUtil {
|
|||
} catch (InvalidUnidentifiedAccessHeaderException e) {
|
||||
Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
|
||||
if (useGroupSendEndorsements && RemoteConfig.internalUser()) {
|
||||
GroupSendEndorsementInternalNotifier.postGroupSendFallbackError(context);
|
||||
}
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, "No session. Falling back to legacy sends.", e);
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
|
@ -377,15 +468,32 @@ public final class GroupSendUtil {
|
|||
Log.i(TAG, "Need to do a legacy send to send a sync message for a group of only ourselves.");
|
||||
}
|
||||
|
||||
List<SignalServiceAddress> targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
|
||||
List<SignalServiceAddress> legacyTargetAddresses = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList());
|
||||
List<UnidentifiedAccess> legacyTargetAccesses = legacyTargets.stream().map(r -> recipients.getAccess(r.getId())).collect(Collectors.toList());
|
||||
List<GroupSendFullToken> groupSendTokens = null;
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.isEmpty();
|
||||
|
||||
if (useGroupSendEndorsements) {
|
||||
Instant expiration = Instant.ofEpochMilli(groupSendEndorsementExpiration);
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupRecord.get().requireV2GroupProperties().getGroupMasterKey());
|
||||
|
||||
groupSendTokens = new ArrayList<>(legacyTargetAddresses.size());
|
||||
|
||||
for (Recipient r : legacyTargets) {
|
||||
GroupSendEndorsement endorsement = groupSendEndorsementRecords.getEndorsement(r.getId());
|
||||
if (r.getHasAci() && endorsement != null) {
|
||||
groupSendTokens.add(endorsement.toFullToken(groupSecretParams, expiration));
|
||||
} else {
|
||||
groupSendTokens.add(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final MessageSendLogTables messageLogDatabase = SignalDatabase.messageLog();
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, legacyTargets, access, recipientUpdate, result -> {
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, legacyTargetAddresses, legacyTargets, SealedSenderAccess.forFanOutGroupSend(groupSendTokens, SealedSenderAccessUtil.getSealedSenderCertificate(), legacyTargetAccesses), recipientUpdate, result -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
|
@ -402,7 +510,7 @@ public final class GroupSendUtil {
|
|||
allResults.addAll(results);
|
||||
|
||||
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
|
||||
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
|
||||
Log.d(TAG, "Successfully sent using 1:1 to " + successCount + "/" + legacyTargetAddresses.size() + " legacy targets.");
|
||||
} else if (relatedMessageId != null) {
|
||||
SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
|
||||
SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
|
||||
|
@ -448,6 +556,7 @@ public final class GroupSendUtil {
|
|||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException;
|
||||
|
@ -455,7 +564,7 @@ public final class GroupSendUtil {
|
|||
@NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
|
@ -504,19 +613,20 @@ public final class GroupSendUtil {
|
|||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
SenderKeyGroupEvents listener = relatedMessageId != null ? new SenderKeyMetricEventListener(relatedMessageId.getId()) : SenderKeyGroupEvents.EMPTY;
|
||||
return messageSender.sendGroupDataMessage(distributionId, targets, access, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
|
||||
return messageSender.sendGroupDataMessage(distributionId, targets, access, groupSendEndorsements, isRecipientUpdate, contentHint, message, listener, urgent, isForStory, editMessage, partialListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
|
@ -524,13 +634,14 @@ public final class GroupSendUtil {
|
|||
{
|
||||
// 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) {
|
||||
Recipient targetRecipient = targetRecipients.get(0);
|
||||
SendMessageResult result;
|
||||
Recipient targetRecipient = targetRecipients.get(0);
|
||||
SealedSenderAccess sealedSenderAccess = sealedSenderAccesses.get(0);
|
||||
SendMessageResult result;
|
||||
|
||||
if (editMessage != null) {
|
||||
result = messageSender.sendEditMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
|
||||
result = messageSender.sendEditMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, editMessage.getTargetSentTimestamp());
|
||||
} else {
|
||||
result = messageSender.sendDataMessage(targets.get(0), access.get(0), contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
|
||||
result = messageSender.sendDataMessage(targets.get(0), sealedSenderAccess, contentHint, message, SignalServiceMessageSender.IndividualSendEvents.EMPTY, urgent, targetRecipient.getNeedsPniSignature());
|
||||
}
|
||||
|
||||
if (targetRecipient.getNeedsPniSignature()) {
|
||||
|
@ -540,10 +651,11 @@ public final class GroupSendUtil {
|
|||
return Collections.singletonList(result);
|
||||
} else {
|
||||
LegacyGroupEvents listener = relatedMessageId != null ? new LegacyMetricEventListener(relatedMessageId.getId()) : LegacyGroupEvents.EMPTY;
|
||||
|
||||
if (editMessage != null) {
|
||||
return messageSender.sendEditMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
|
||||
return messageSender.sendEditMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent, editMessage.getTargetSentTimestamp());
|
||||
} else {
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
return messageSender.sendDataMessage(targets, sealedSenderAccesses, isRecipientUpdate, contentHint, message, listener, partialListener, cancelationSignal, urgent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -591,11 +703,12 @@ public final class GroupSendUtil {
|
|||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
messageSender.sendGroupTyping(distributionId, targets, access, message);
|
||||
messageSender.sendGroupTyping(distributionId, targets, access, groupSendEndorsements, message);
|
||||
List<SendMessageResult> results = targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
|
||||
|
||||
if (partialListener != null) {
|
||||
|
@ -609,13 +722,13 @@ public final class GroupSendUtil {
|
|||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
messageSender.sendTyping(targets, access, message, cancelationSignal);
|
||||
messageSender.sendTyping(targets, sealedSenderAccesses, message, cancelationSignal);
|
||||
return targets.stream().map(a -> SendMessageResult.success(a, Collections.emptyList(), true, false, -1, Optional.empty())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -658,24 +771,25 @@ public final class GroupSendUtil {
|
|||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialSendListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
return messageSender.sendCallMessage(distributionId, targets, access, message, partialSendListener);
|
||||
return messageSender.sendCallMessage(distributionId, targets, access, groupSendEndorsements, message, partialSendListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
return messageSender.sendCallMessage(targets, access, message);
|
||||
return messageSender.sendCallMessage(targets, sealedSenderAccesses, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -730,18 +844,19 @@ public final class GroupSendUtil {
|
|||
@NonNull DistributionId distributionId,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<UnidentifiedAccess> access,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendBatchCompleteListener partialListener)
|
||||
throws NoSessionException, UntrustedIdentityException, InvalidKeyException, IOException, InvalidRegistrationIdException
|
||||
{
|
||||
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
|
||||
return messageSender.sendGroupStory(distributionId, Optional.ofNullable(groupId).map(GroupId::getDecodedId), targets, access, groupSendEndorsements, isRecipientUpdate, message, getSentTimestamp(), manifest, partialListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<SendMessageResult> sendLegacy(@NonNull SignalServiceMessageSender messageSender,
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Recipient> targetRecipients,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
@NonNull List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
|
@ -839,12 +954,12 @@ public final class GroupSendUtil {
|
|||
*/
|
||||
private static final class RecipientData {
|
||||
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccessPair>> accessById;
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccess>> accessById;
|
||||
private final Map<RecipientId, SignalServiceAddress> addressById;
|
||||
private final RecipientAccessList accessList;
|
||||
|
||||
RecipientData(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) throws IOException {
|
||||
this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients, isForStory);
|
||||
this.accessById = SealedSenderAccessUtil.getAccessMapFor(recipients, isForStory);
|
||||
this.addressById = mapAddresses(context, recipients);
|
||||
this.accessList = new RecipientAccessList(recipients);
|
||||
}
|
||||
|
@ -853,22 +968,22 @@ public final class GroupSendUtil {
|
|||
return Objects.requireNonNull(addressById.get(id));
|
||||
}
|
||||
|
||||
@NonNull Optional<UnidentifiedAccessPair> getAccessPair(@NonNull RecipientId id) {
|
||||
@NonNull Optional<UnidentifiedAccess> getAccessPair(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id));
|
||||
}
|
||||
|
||||
@Nullable UnidentifiedAccess getAccess(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id)).orElse(null);
|
||||
}
|
||||
|
||||
@NonNull UnidentifiedAccess requireAccess(@NonNull RecipientId id) {
|
||||
return Objects.requireNonNull(accessById.get(id)).get().getTargetUnidentifiedAccess().get();
|
||||
return Objects.requireNonNull(accessById.get(id)).get();
|
||||
}
|
||||
|
||||
@NonNull RecipientId requireRecipientId(@NonNull SignalServiceAddress address) {
|
||||
return accessList.requireIdByAddress(address);
|
||||
}
|
||||
|
||||
@NonNull List<RecipientId> requireRecipientIds(@NonNull List<SignalServiceAddress> addresses) {
|
||||
return addresses.stream().map(accessList::requireIdByAddress).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static @NonNull Map<RecipientId, SignalServiceAddress> mapAddresses(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage
|
|||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.BadGroupIdException
|
||||
|
@ -137,7 +137,7 @@ object MessageDecryptor {
|
|||
|
||||
val bufferedStore = bufferedProtocolStore.get(destination)
|
||||
val localAddress = SignalServiceAddress(selfAci, SignalStore.account.e164)
|
||||
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator())
|
||||
val cipher = SignalServiceCipher(localAddress, SignalStore.account.deviceId, bufferedStore, ReentrantSessionLock.INSTANCE, SealedSenderAccessUtil.getCertificateValidator())
|
||||
|
||||
return try {
|
||||
val startTimeNanos = System.nanoTime()
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.thoughtcrime.securesms.database.RecipientTable.MentionSetting
|
|||
import org.thoughtcrime.securesms.database.RecipientTable.MissingRecipientException
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.PhoneNumberSharingState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.UnidentifiedAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.SealedSenderAccessMode
|
||||
import org.thoughtcrime.securesms.database.RecipientTable.VibrateState
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
|
@ -98,7 +98,7 @@ class Recipient(
|
|||
val hiddenState: HiddenState = HiddenState.NOT_HIDDEN,
|
||||
val lastProfileFetchTime: Long = 0,
|
||||
private val notificationChannelValue: String? = null,
|
||||
private val unidentifiedAccessModeValue: UnidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN,
|
||||
private val sealedSenderAccessModeValue: SealedSenderAccessMode = SealedSenderAccessMode.UNKNOWN,
|
||||
private val capabilities: RecipientRecord.Capabilities = RecipientRecord.Capabilities.UNKNOWN,
|
||||
val storageId: ByteArray? = null,
|
||||
val mentionSetting: MentionSetting = MentionSetting.ALWAYS_NOTIFY,
|
||||
|
@ -318,10 +318,10 @@ class Recipient(
|
|||
val deleteSyncCapability: Capability = capabilities.deleteSync
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val unidentifiedAccessMode: UnidentifiedAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
UnidentifiedAccessMode.DISABLED
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
SealedSenderAccessMode.DISABLED
|
||||
} else {
|
||||
unidentifiedAccessModeValue
|
||||
sealedSenderAccessModeValue
|
||||
}
|
||||
|
||||
/** The wallpaper to render as the chat background, if present. */
|
||||
|
@ -760,7 +760,7 @@ class Recipient(
|
|||
systemProfileName == other.systemProfileName &&
|
||||
profileAvatar == other.profileAvatar &&
|
||||
notificationChannelValue == other.notificationChannelValue &&
|
||||
unidentifiedAccessModeValue == other.unidentifiedAccessModeValue &&
|
||||
sealedSenderAccessModeValue == other.sealedSenderAccessModeValue &&
|
||||
storageId.contentEquals(other.storageId) &&
|
||||
mentionSetting == other.mentionSetting &&
|
||||
wallpaperValue == other.wallpaperValue &&
|
||||
|
|
|
@ -162,7 +162,7 @@ object RecipientCreator {
|
|||
lastProfileFetchTime = record.lastProfileFetch,
|
||||
isSelf = isSelf,
|
||||
notificationChannelValue = record.notificationChannel,
|
||||
unidentifiedAccessModeValue = record.unidentifiedAccessMode,
|
||||
sealedSenderAccessModeValue = record.sealedSenderAccessMode,
|
||||
capabilities = record.capabilities,
|
||||
storageId = record.storageId,
|
||||
mentionSetting = record.mentionSetting,
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.signal.ringrtc.PeekInfo;
|
|||
import org.signal.ringrtc.Remote;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.CallLinkTable;
|
||||
import org.thoughtcrime.securesms.database.CallTable;
|
||||
import org.thoughtcrime.securesms.database.GroupTable;
|
||||
|
@ -66,8 +66,8 @@ 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.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore;
|
||||
|
@ -75,6 +75,7 @@ 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.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||
|
@ -780,8 +781,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
try {
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
recipient.isSelf() ? Optional.empty() : UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
recipient.isSelf() ? SealedSenderAccess.NONE : SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
callMessage);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.i(TAG, "onSendCallMessage onFailure: ", e);
|
||||
RetrieveProfileJob.enqueue(recipient.getId());
|
||||
|
@ -1129,8 +1130,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
try {
|
||||
AppDependencies.getSignalServiceMessageSender()
|
||||
.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
|
||||
UnidentifiedAccessUtil.getAccessFor(context, recipient),
|
||||
callMessage);
|
||||
SealedSenderAccessUtil.getSealedSenderAccessFor(recipient),
|
||||
callMessage);
|
||||
process((s, p) -> p.handleMessageSentSuccess(s, remotePeer.getCallId()));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
RetrieveProfileJob.enqueue(remotePeer.getId());
|
||||
|
@ -1158,7 +1159,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
|
@ -1175,7 +1176,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, isVideoCall);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
|
@ -1188,7 +1189,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
|||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
SyncMessage.CallEvent callEvent = CallEventSyncMessageUtil.createNotAcceptedSyncMessage(remotePeer, System.currentTimeMillis(), isOutgoing, true);
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent), Optional.empty());
|
||||
AppDependencies.getSignalServiceMessageSender().sendSyncMessage(SignalServiceSyncMessage.forCallEvent(callEvent));
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
Log.w(TAG, "Unable to send call event sync message for " + remotePeer.getCallId().longValue(), e);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
|||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.RecipientTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
|
@ -38,8 +38,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.profiles.AvatarUploadParams;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
@ -118,7 +117,7 @@ public final class ProfileUtil {
|
|||
|
||||
ServiceResponse<ProfileAndCredential> response = Single
|
||||
.fromCallable(() -> new SignalServiceAddress(pni))
|
||||
.flatMap(address -> profileService.getProfile(address, Optional.empty(), Optional.empty(), requestType, Locale.getDefault()))
|
||||
.flatMap(address -> profileService.getProfile(address, Optional.empty(), SealedSenderAccess.NONE, requestType, Locale.getDefault()))
|
||||
.onErrorReturn(t -> ServiceResponse.forUnknownError(t))
|
||||
.blockingGet();
|
||||
|
||||
|
@ -137,12 +136,12 @@ public final class ProfileUtil {
|
|||
@NonNull SignalServiceProfile.RequestType requestType,
|
||||
boolean allowUnidentifiedAccess)
|
||||
{
|
||||
ProfileService profileService = AppDependencies.getProfileService();
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess = allowUnidentifiedAccess ? getUnidentifiedAccess(context, recipient) : Optional.empty();
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
ProfileService profileService = AppDependencies.getProfileService();
|
||||
SealedSenderAccess sealedSenderAccess = allowUnidentifiedAccess ? SealedSenderAccessUtil.getSealedSenderAccessFor(recipient, false) : SealedSenderAccess.NONE;
|
||||
Optional<ProfileKey> profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey());
|
||||
|
||||
return Single.fromCallable(() -> toSignalServiceAddress(context, recipient))
|
||||
.flatMap(address -> profileService.getProfile(address, profileKey, unidentifiedAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
|
||||
.flatMap(address -> profileService.getProfile(address, profileKey, sealedSenderAccess, requestType, Locale.getDefault()).map(p -> new Pair<>(recipient, p)))
|
||||
.onErrorReturn(t -> new Pair<>(recipient, ServiceResponse.forUnknownError(t)));
|
||||
}
|
||||
|
||||
|
@ -397,16 +396,6 @@ public final class ProfileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private static Optional<UnidentifiedAccess> getUnidentifiedAccess(@NonNull Context context, @NonNull Recipient recipient) {
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess = UnidentifiedAccessUtil.getAccessFor(context, recipient, false);
|
||||
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static @NonNull SignalServiceAddress toSignalServiceAddress(@NonNull Context context, @NonNull Recipient recipient) throws IOException {
|
||||
if (recipient.getRegistered() == RecipientTable.RegisteredState.NOT_REGISTERED) {
|
||||
if (recipient.getHasServiceId()) {
|
||||
|
|
|
@ -67,7 +67,7 @@ class ChangeSet {
|
|||
}
|
||||
|
||||
fun toApiResponse(): GroupHistoryPage {
|
||||
return GroupHistoryPage(changeSet.map { DecryptedGroupChangeLog(it.groupSnapshot, it.groupChange) }, GroupHistoryPage.PagingData.NONE)
|
||||
return GroupHistoryPage(changeSet.map { DecryptedGroupChangeLog(it.groupSnapshot, it.groupChange) }, null, GroupHistoryPage.PagingData.NONE)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,8 @@ fun groupRecord(
|
|||
decryptedGroup.revision,
|
||||
decryptedGroup.encode(),
|
||||
distributionId,
|
||||
System.currentTimeMillis()
|
||||
System.currentTimeMillis(),
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ object RecipientDatabaseTestUtils {
|
|||
profileSharing: Boolean = false,
|
||||
lastProfileFetch: Long = 0L,
|
||||
notificationChannel: String? = null,
|
||||
unidentifiedAccessMode: RecipientTable.UnidentifiedAccessMode = RecipientTable.UnidentifiedAccessMode.UNKNOWN,
|
||||
sealedSenderAccessMode: RecipientTable.SealedSenderAccessMode = RecipientTable.SealedSenderAccessMode.UNKNOWN,
|
||||
capabilities: Long = 0L,
|
||||
storageId: ByteArray? = null,
|
||||
mentionSetting: RecipientTable.MentionSetting = RecipientTable.MentionSetting.ALWAYS_NOTIFY,
|
||||
|
@ -121,7 +121,7 @@ object RecipientDatabaseTestUtils {
|
|||
profileSharing = profileSharing,
|
||||
lastProfileFetch = lastProfileFetch,
|
||||
notificationChannel = notificationChannel,
|
||||
unidentifiedAccessMode = unidentifiedAccessMode,
|
||||
sealedSenderAccessMode = sealedSenderAccessMode,
|
||||
capabilities = RecipientRecord.Capabilities(
|
||||
rawBits = capabilities,
|
||||
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, RecipientTable.Capabilities.DELETE_SYNC, RecipientTable.Capabilities.BIT_LENGTH).toInt())
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.signal.libsignal.protocol.logging.SignalProtocolLogger
|
|||
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse
|
||||
import org.signal.storageservice.protos.groups.Member
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||
|
@ -111,9 +112,9 @@ class GroupManagerV2Test_edit {
|
|||
|
||||
every { groupTable.getGroup(groupId) } returns data.groupRecord
|
||||
every { groupTable.requireGroup(groupId) } returns data.groupRecord.get()
|
||||
every { groupTable.update(any<GroupId.V2>(), any()) } returns Unit
|
||||
every { groupTable.update(any<GroupId.V2>(), any(), any()) } returns Unit
|
||||
every { sendGroupUpdateHelper.sendGroupUpdate(masterKey, any(), any(), any()) } returns GroupManagerV2.RecipientAndThread(Recipient.UNKNOWN, 1)
|
||||
every { groupsV2API.patchGroup(any(), any(), any()) } returns data.groupChange!!
|
||||
every { groupsV2API.patchGroup(any(), any(), any()) } returns GroupChangeResponse(groupChange = data.groupChange!!)
|
||||
}
|
||||
|
||||
private fun editGroup(perform: GroupManagerV2.GroupEditor.() -> Unit) {
|
||||
|
@ -122,7 +123,7 @@ class GroupManagerV2Test_edit {
|
|||
|
||||
private fun then(then: (DecryptedGroup) -> Unit) {
|
||||
val decryptedGroupArg = slot<DecryptedGroup>()
|
||||
verify { groupTable.update(groupId, capture(decryptedGroupArg)) }
|
||||
verify { groupTable.update(groupId, capture(decryptedGroupArg), any()) }
|
||||
then(decryptedGroupArg.captured)
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ public final class GroupStatePatcherTest {
|
|||
|
||||
@Test
|
||||
public void unknown_group_with_no_states_to_update() {
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, emptyList()), 10);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, emptyList(), null), 10);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(emptyList()));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -52,7 +52,7 @@ public final class GroupStatePatcherTest {
|
|||
public void known_group_with_no_states_to_update() {
|
||||
DecryptedGroup currentState = state(0);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, emptyList()), 10);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, emptyList(), null), 10);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(emptyList()));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -63,7 +63,7 @@ public final class GroupStatePatcherTest {
|
|||
public void unknown_group_single_state_to_update() {
|
||||
DecryptedGroupChangeLog log0 = serverLogEntry(0);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, singletonList(log0)), 10);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, singletonList(log0), null), 10);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(asLocal(log0))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -75,7 +75,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroup currentState = state(0);
|
||||
DecryptedGroupChangeLog log1 = serverLogEntry(1);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log1)), 1);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log1), null), 1);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(asLocal(log1))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -88,7 +88,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log1 = serverLogEntry(1);
|
||||
DecryptedGroupChangeLog log2 = serverLogEntry(2);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2)), 2);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2), null), 2);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), asLocal(log2))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -101,7 +101,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log1 = serverLogEntry(1);
|
||||
DecryptedGroupChangeLog log2 = serverLogEntry(2);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2)), 2);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2), null), 2);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(asLocal(log2))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -115,7 +115,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log2 = serverLogEntry(2);
|
||||
DecryptedGroupChangeLog log3 = serverLogEntry(3);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3)), 2);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3), null), 2);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), asLocal(log2))));
|
||||
assertNewState(log2.getGroup(), singletonList(log3), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -129,7 +129,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log2 = serverLogEntry(2);
|
||||
DecryptedGroupChangeLog log3 = serverLogEntry(3);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), asLocal(log2), asLocal(log3))));
|
||||
assertNewState(log3.getGroup(), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -142,7 +142,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log1 = serverLogEntry(Integer.MAX_VALUE - 1);
|
||||
DecryptedGroupChangeLog log2 = serverLogEntry(Integer.MAX_VALUE);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), asLocal(log2))));
|
||||
assertNewState(log2.getGroup(), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -153,7 +153,7 @@ public final class GroupStatePatcherTest {
|
|||
public void unknown_group_single_state_to_update_with_missing_change() {
|
||||
DecryptedGroupChangeLog log0 = serverLogEntryWholeStateOnly(0);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, singletonList(log0)), 10);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(null, singletonList(log0), null), 10);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(asLocal(log0))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -165,7 +165,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroup currentState = state(0);
|
||||
DecryptedGroupChangeLog log1 = serverLogEntryWholeStateOnly(1);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log1)), 1);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log1), null), 1);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(localLogEntryNoEditor(1))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -179,7 +179,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log2 = serverLogEntryWholeStateOnly(2);
|
||||
DecryptedGroupChangeLog log3 = serverLogEntry(3);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2, log3), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), localLogEntryNoEditor(2), asLocal(log3))));
|
||||
assertNewState(log3.getGroup(), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -192,7 +192,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log1 = serverLogEntry(1);
|
||||
DecryptedGroupChangeLog log3 = serverLogEntry(3);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log3)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log3), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1), asLocal(log3))));
|
||||
assertNewState(log3.getGroup(), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -220,7 +220,7 @@ public final class GroupStatePatcherTest {
|
|||
.build();
|
||||
DecryptedGroupChangeLog log4 = new DecryptedGroupChangeLog(state4, change(4));
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log3, log4)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log3, log4), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1),
|
||||
new AppliedGroupChangeLog(state3a, log3.getChange()),
|
||||
|
@ -241,7 +241,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log7 = serverLogEntryWholeStateOnly(7);
|
||||
DecryptedGroupChangeLog log8 = serverLogEntryWholeStateOnly(8);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log6, log7, log8)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log6, log7, log8), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(localLogEntryNoEditor(6), localLogEntryNoEditor(7), localLogEntryNoEditor(8))));
|
||||
assertNewState(log8.getGroup(), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -255,7 +255,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroupChangeLog log8 = logEntryMissingState(8);
|
||||
DecryptedGroupChangeLog log9 = logEntryMissingState(9);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log7, log8, log9)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log7, log8, log9), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(serverLogEntry(7)), asLocal(serverLogEntry(8)), asLocal(serverLogEntry(9)))));
|
||||
assertNewState(state(9), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -287,7 +287,7 @@ public final class GroupStatePatcherTest {
|
|||
.build(),
|
||||
change(9));
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log7, log8, log9)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log7, log8, log9), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log7),
|
||||
new AppliedGroupChangeLog(state7b, log8.getChange()),
|
||||
|
@ -305,7 +305,7 @@ public final class GroupStatePatcherTest {
|
|||
DecryptedGroup currentState = state(6);
|
||||
DecryptedGroupChangeLog log6 = serverLogEntryWholeStateOnly(6);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log6)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log6), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(emptyList()));
|
||||
assertNewState(state(6), emptyList(), advanceGroupStateResult.getUpdatedGroupState(), advanceGroupStateResult.getRemainingRemoteGroupChanges());
|
||||
|
@ -320,7 +320,7 @@ public final class GroupStatePatcherTest {
|
|||
.build();
|
||||
DecryptedGroupChangeLog log6 = serverLogEntry(6);
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log6)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log6), null), LATEST);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(asLocal(log6))));
|
||||
assertTrue(advanceGroupStateResult.getRemainingRemoteGroupChanges().isEmpty());
|
||||
|
@ -352,7 +352,7 @@ public final class GroupStatePatcherTest {
|
|||
.newMembers(Collections.singletonList(newMember))
|
||||
.build());
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8), null), LATEST);
|
||||
|
||||
assertNotNull(log8.getGroup());
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(emptyList()));
|
||||
|
@ -392,7 +392,7 @@ public final class GroupStatePatcherTest {
|
|||
.newMembers(Collections.singletonList(newMember))
|
||||
.build();
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8), null), LATEST);
|
||||
|
||||
assertNotNull(log8.getGroup());
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(new AppliedGroupChangeLog(log8.getGroup(), expectedChange))));
|
||||
|
@ -433,7 +433,7 @@ public final class GroupStatePatcherTest {
|
|||
.newAvatar(new DecryptedString.Builder().value_("Group Avatar " + 8).build())
|
||||
.build();
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8)), LATEST);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, singletonList(log8), null), LATEST);
|
||||
|
||||
assertNotNull(log8.getGroup());
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(singletonList(new AppliedGroupChangeLog(log8.getGroup(), expectedChange))));
|
||||
|
@ -454,7 +454,7 @@ public final class GroupStatePatcherTest {
|
|||
.newTitle(new DecryptedString.Builder().value_(log1.getGroup().title).build())
|
||||
.build());
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2)), 2);
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStatePatcher.applyGroupStateDiff(new GroupStateDiff(currentState, asList(log1, log2), null), 2);
|
||||
|
||||
assertThat(advanceGroupStateResult.getProcessedLogEntries(), is(asList(asLocal(log1),
|
||||
new AppliedGroupChangeLog(log2.getGroup(), new DecryptedGroupChange.Builder()
|
||||
|
|
|
@ -56,9 +56,12 @@ import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
|
|||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
|
||||
import org.thoughtcrime.securesms.testutil.SystemOutLogger
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
|
||||
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException
|
||||
import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
|
@ -88,6 +91,7 @@ class GroupsV2StateProcessorTest {
|
|||
private lateinit var recipientTable: RecipientTable
|
||||
private lateinit var groupsV2API: GroupsV2Api
|
||||
private lateinit var groupsV2Authorization: GroupsV2Authorization
|
||||
private lateinit var groupsV2Operations: GroupsV2Operations
|
||||
private lateinit var profileAndMessageHelper: ProfileAndMessageHelper
|
||||
private lateinit var jobManager: JobManager
|
||||
|
||||
|
@ -104,6 +108,7 @@ class GroupsV2StateProcessorTest {
|
|||
groupTable = mockk()
|
||||
recipientTable = mockk()
|
||||
groupsV2API = mockk()
|
||||
groupsV2Operations = mockk()
|
||||
groupsV2Authorization = mockk()
|
||||
profileAndMessageHelper = spyk(ProfileAndMessageHelper(serviceIds.aci, masterKey, groupId))
|
||||
jobManager = mockk()
|
||||
|
@ -112,6 +117,7 @@ class GroupsV2StateProcessorTest {
|
|||
every { AppDependencies.jobManager } returns jobManager
|
||||
every { AppDependencies.signalServiceAccountManager.getGroupsV2Api() } returns groupsV2API
|
||||
every { AppDependencies.groupsV2Authorization } returns groupsV2Authorization
|
||||
every { AppDependencies.groupsV2Operations } returns groupsV2Operations
|
||||
|
||||
mockkObject(SignalDatabase)
|
||||
every { SignalDatabase.groups } returns groupTable
|
||||
|
@ -120,6 +126,8 @@ class GroupsV2StateProcessorTest {
|
|||
mockkObject(ProfileAndMessageHelper)
|
||||
every { ProfileAndMessageHelper.create(any(), any(), any()) } returns profileAndMessageHelper
|
||||
|
||||
every { groupsV2Operations.forGroup(secretParams) } answers { callOriginal() }
|
||||
|
||||
processor = GroupsV2StateProcessor.forGroup(serviceIds, masterKey, secretParams)
|
||||
}
|
||||
|
||||
|
@ -142,11 +150,11 @@ class GroupsV2StateProcessorTest {
|
|||
every { groupsV2Authorization.getAuthorizationForToday(serviceIds, secretParams) } returns null
|
||||
|
||||
if (data.expectTableUpdate) {
|
||||
justRun { groupTable.update(any<GroupMasterKey>(), any<DecryptedGroup>()) }
|
||||
justRun { groupTable.update(any<GroupMasterKey>(), any<DecryptedGroup>(), any<ReceivedGroupSendEndorsements>()) }
|
||||
}
|
||||
|
||||
if (data.expectTableCreate) {
|
||||
every { groupTable.create(any<GroupMasterKey>(), any<DecryptedGroup>()) } returns groupId
|
||||
every { groupTable.create(any<GroupMasterKey>(), any<DecryptedGroup>(), any<ReceivedGroupSendEndorsements>()) } returns groupId
|
||||
}
|
||||
|
||||
if (data.expectTableUpdate || data.expectTableCreate) {
|
||||
|
@ -155,11 +163,11 @@ class GroupsV2StateProcessorTest {
|
|||
}
|
||||
|
||||
data.serverState?.let { serverState ->
|
||||
every { groupsV2API.getGroup(any(), any()) } returns serverState
|
||||
every { groupsV2API.getGroup(any(), any()) } returns DecryptedGroupResponse(serverState, null)
|
||||
}
|
||||
|
||||
data.changeSet?.let { changeSet ->
|
||||
every { groupsV2API.getGroupHistoryPage(any(), data.requestedRevision, any(), data.includeFirst) } returns changeSet.toApiResponse()
|
||||
every { groupsV2API.getGroupHistoryPage(any(), data.requestedRevision, any(), data.includeFirst, 0) } returns changeSet.toApiResponse()
|
||||
}
|
||||
|
||||
every { groupsV2API.getGroupAsResult(any(), any()) } answers { callOriginal() }
|
||||
|
@ -241,7 +249,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("title changed to match server", result.latestServer!!.title, `is`("Asdf"))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -279,7 +287,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("revision matches server", result.latestServer!!.revision, `is`(7))
|
||||
assertThat("title changed on server to final result", result.latestServer!!.title, `is`("Asdf!"))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -323,7 +331,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("title changed on server to final result", result.latestServer!!.title, `is`("And beyond"))
|
||||
assertThat("Description updated in change after full snapshot", result.latestServer!!.description, `is`("Description"))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -351,7 +359,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("revision matches peer change", result.latestServer!!.revision, `is`(6))
|
||||
assertThat("timer changed by peer change", result.latestServer!!.disappearingMessagesTimer!!.duration, `is`(5000))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -383,7 +391,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("member promoted by peer change", result.latestServer!!.members.map { it.aciBytes }, hasItem(selfAci.toByteString()))
|
||||
|
||||
verify { jobManager.add(ofType(DirectoryRefreshJob::class)) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -426,7 +434,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches server", result.latestServer!!.revision, `is`(2))
|
||||
|
||||
verify { groupsV2API.getGroupHistoryPage(secretParams, 1, any(), false) }
|
||||
verify { groupsV2API.getGroupHistoryPage(secretParams, 1, any(), false, 0) }
|
||||
|
||||
unmockkStatic(DecryptedGroupUtil::class)
|
||||
}
|
||||
|
@ -485,7 +493,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches server", result.latestServer!!.revision, `is`(3))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -513,7 +521,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches server", result.latestServer!!.revision, `is`(2))
|
||||
|
||||
verify { groupTable.create(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.create(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -551,7 +559,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("title matches that as it was in revision added", result.latestServer!!.title, `is`("Baking Signal for Science"))
|
||||
|
||||
verify { jobManager.add(ofType(RequestGroupV2InfoJob::class)) }
|
||||
verify { groupTable.create(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.create(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -577,7 +585,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches latest server", result.latestServer!!.revision, `is`(10))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -613,7 +621,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches server", result.latestServer!!.revision, `is`(3))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -662,7 +670,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("title matches revision approved at", result.latestServer!!.title, `is`("Beam me up"))
|
||||
|
||||
verify { jobManager.add(ofType(RequestGroupV2InfoJob::class)) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -705,7 +713,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("local should update to server", result.updateStatus, `is`(GroupUpdateResult.UpdateStatus.GROUP_UPDATED))
|
||||
assertThat("revision matches latest revision on server", result.latestServer!!.revision, `is`(101))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -756,7 +764,7 @@ class GroupsV2StateProcessorTest {
|
|||
|
||||
assertThat("group update messages contains new member add", updateMessageContextArgs.map { it.change!!.newMembers }, hasItem(hasItem(secondOther)))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -792,7 +800,7 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("group update messages contains new member add", updateMessageContextArgs.map { it.change!!.newMembers }, hasItem(hasItem(secondOther)))
|
||||
assertThat("group update messages contains title change", updateMessageContextArgs.mapNotNull { it.change!!.newTitle }.any { it.value_ == "Changed" })
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -874,6 +882,6 @@ class GroupsV2StateProcessorTest {
|
|||
assertThat("revision matches server", result.latestServer!!.revision, `is`(10))
|
||||
assertThat("title changed on server to final result", result.latestServer!!.title, `is`("Asdf!"))
|
||||
|
||||
verify { groupTable.update(masterKey, result.latestServer!!) }
|
||||
verify { groupTable.update(masterKey, result.latestServer!!, null) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.signalservice.api;
|
|||
|
||||
import com.squareup.wire.FieldEncoding;
|
||||
|
||||
import org.signal.core.util.Base64;
|
||||
import org.signal.libsignal.net.Network;
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
|
@ -26,6 +27,7 @@ import org.whispersystems.signalservice.api.account.PreKeyUpload;
|
|||
import org.whispersystems.signalservice.api.archive.ArchiveApi;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipherOutputStream;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
|
@ -88,7 +90,6 @@ import org.whispersystems.signalservice.internal.storage.protos.WriteOperation;
|
|||
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
|
||||
import org.signal.core.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
|
@ -757,7 +758,7 @@ public class SignalServiceAccountManager {
|
|||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
try {
|
||||
ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId, profileKey, Optional.empty(), locale).get(10, TimeUnit.SECONDS);
|
||||
ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(serviceId, profileKey, SealedSenderAccess.NONE, locale).get(10, TimeUnit.SECONDS);
|
||||
return credential.getExpiringProfileKeyCredential();
|
||||
} catch (InterruptedException | TimeoutException e) {
|
||||
throw new PushNetworkException(e);
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.whispersystems.signalservice.api.backup.BackupKey;
|
|||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipherInputStream;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
@ -98,7 +98,7 @@ public class SignalServiceMessageReceiver {
|
|||
|
||||
public ListenableFuture<ProfileAndCredential> retrieveProfile(SignalServiceAddress address,
|
||||
Optional<ProfileKey> profileKey,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
SignalServiceProfile.RequestType requestType,
|
||||
Locale locale)
|
||||
{
|
||||
|
@ -115,16 +115,16 @@ public class SignalServiceMessageReceiver {
|
|||
}
|
||||
|
||||
if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) {
|
||||
return socket.retrieveVersionedProfileAndCredential(aci, profileKey.get(), unidentifiedAccess, locale);
|
||||
return socket.retrieveVersionedProfileAndCredential(aci, profileKey.get(), sealedSenderAccess, locale);
|
||||
} else {
|
||||
return FutureTransformers.map(socket.retrieveVersionedProfile(aci, profileKey.get(), unidentifiedAccess, locale), profile -> {
|
||||
return FutureTransformers.map(socket.retrieveVersionedProfile(aci, profileKey.get(), sealedSenderAccess, locale), profile -> {
|
||||
return new ProfileAndCredential(profile,
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.empty());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return FutureTransformers.map(socket.retrieveProfile(address, unidentifiedAccess, locale), profile -> {
|
||||
return FutureTransformers.map(socket.retrieveProfile(address, sealedSenderAccess, locale), profile -> {
|
||||
return new ProfileAndCredential(profile,
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.empty());
|
||||
|
@ -146,8 +146,8 @@ public class SignalServiceMessageReceiver {
|
|||
return new FileInputStream(destination);
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull IdentityCheckRequest request, @Nonnull Optional<UnidentifiedAccess> unidentifiedAccess, @Nonnull ResponseMapper<IdentityCheckResponse> responseMapper) {
|
||||
return socket.performIdentityCheck(request, unidentifiedAccess, responseMapper);
|
||||
public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull IdentityCheckRequest request, @Nonnull ResponseMapper<IdentityCheckResponse> responseMapper) {
|
||||
return socket.performIdentityCheck(request, responseMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,16 +22,18 @@ 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.protocol.util.Pair;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeContent;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
|
@ -92,8 +94,8 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
|
|||
import org.whispersystems.signalservice.internal.crypto.AttachmentDigest;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentPointer;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentUploadForm;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
|
||||
import org.whispersystems.signalservice.internal.push.BodyRange;
|
||||
import org.whispersystems.signalservice.internal.push.CallMessage;
|
||||
import org.whispersystems.signalservice.internal.push.Content;
|
||||
|
@ -131,7 +133,6 @@ import org.whispersystems.signalservice.internal.push.http.PartialSendBatchCompl
|
|||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.util.ByteArrayUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -145,10 +146,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@ -223,7 +222,7 @@ public class SignalServiceMessageSender {
|
|||
* @param message The read receipt to deliver.
|
||||
*/
|
||||
public SendMessageResult sendReceipt(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
SignalServiceReceiptMessage message,
|
||||
boolean includePniSignature)
|
||||
throws IOException, UntrustedIdentityException
|
||||
|
@ -240,14 +239,14 @@ public class SignalServiceMessageSender {
|
|||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
return sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), message.getWhen(), envelopeContent, false, null, null, false, false);
|
||||
return sendMessage(recipient, sealedSenderAccess, message.getWhen(), envelopeContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a retry receipt for a bad-encrypted envelope.
|
||||
*/
|
||||
public void sendRetryReceipt(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
Optional<byte[]> groupId,
|
||||
DecryptionErrorMessage errorMessage)
|
||||
throws IOException, UntrustedIdentityException
|
||||
|
@ -258,16 +257,16 @@ public class SignalServiceMessageSender {
|
|||
PlaintextContent content = new PlaintextContent(errorMessage);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.plaintext(content, groupId);
|
||||
|
||||
sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, null, false, false);
|
||||
sendMessage(recipient, sealedSenderAccess, System.currentTimeMillis(), envelopeContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a typing indicator using client-side fanout. Doesn't bother with return results, since these are best-effort.
|
||||
*/
|
||||
public void sendTyping(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
SignalServiceTypingMessage message,
|
||||
CancelationSignal cancelationSignal)
|
||||
public void sendTyping(List<SignalServiceAddress> recipients,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
SignalServiceTypingMessage message,
|
||||
CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a typing message to " + recipients.size() + " recipient(s) using 1:1 messages.");
|
||||
|
@ -275,22 +274,23 @@ 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, null, false, false);
|
||||
sendMessage(recipients, sealedSenderAccesses, message.getTimestamp(), envelopeContent, true, null, cancelationSignal, null, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a typing indicator to a group using sender key. Doesn't bother with return results, since these are best-effort.
|
||||
*/
|
||||
public void sendGroupTyping(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
SignalServiceTypingMessage message)
|
||||
public void sendGroupTyping(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
SignalServiceTypingMessage message)
|
||||
throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a typing message to " + recipients.size() + " recipient(s) using sender key.");
|
||||
|
||||
Content content = createTypingContent(message);
|
||||
sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, ContentHint.IMPLICIT, message.getGroupId(), true, SenderKeyGroupEvents.EMPTY, false, false);
|
||||
sendGroupMessage(distributionId, recipients, unidentifiedAccess, groupSendEndorsements, message.getTimestamp(), content, ContentHint.IMPLICIT, message.getGroupId(), true, SenderKeyGroupEvents.EMPTY, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -311,28 +311,29 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
SignalServiceSyncMessage syncMessage = createSelfSendSyncMessageForStory(message, timestamp, isRecipientUpdate, manifest);
|
||||
sendSyncMessage(syncMessage, Optional.empty());
|
||||
sendSyncMessage(syncMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<SendMessageResult> sendGroupStory(DistributionId distributionId,
|
||||
Optional<byte[]> groupId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
boolean isRecipientUpdate,
|
||||
SignalServiceStoryMessage message,
|
||||
long timestamp,
|
||||
public List<SendMessageResult> sendGroupStory(DistributionId distributionId,
|
||||
Optional<byte[]> groupId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
SignalServiceStoryMessage message,
|
||||
long timestamp,
|
||||
Set<SignalServiceStoryMessageRecipient> manifest,
|
||||
PartialSendBatchCompleteListener partialListener)
|
||||
PartialSendBatchCompleteListener partialListener)
|
||||
throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException
|
||||
{
|
||||
Log.d(TAG, "[" + timestamp + "] Sending a story.");
|
||||
|
||||
Content content = createStoryContent(message);
|
||||
List<SendMessageResult> sendMessageResults = sendGroupMessage(distributionId, recipients, unidentifiedAccess, timestamp, content, ContentHint.IMPLICIT, groupId, false, SenderKeyGroupEvents.EMPTY, false, true);
|
||||
List<SendMessageResult> sendMessageResults = sendGroupMessage(distributionId, recipients, unidentifiedAccess, groupSendEndorsements, timestamp, content, ContentHint.IMPLICIT, groupId, false, SenderKeyGroupEvents.EMPTY, false, true);
|
||||
|
||||
if (partialListener != null) {
|
||||
partialListener.onPartialSendComplete(sendMessageResults);
|
||||
|
@ -354,7 +355,7 @@ public class SignalServiceMessageSender {
|
|||
* @throws IOException
|
||||
*/
|
||||
public void sendCallMessage(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
SignalServiceCallMessage message)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
|
@ -364,11 +365,11 @@ public class SignalServiceMessageSender {
|
|||
Content content = createCallContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.DEFAULT, Optional.empty());
|
||||
|
||||
sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, message.isUrgent(), false);
|
||||
sendMessage(recipient, sealedSenderAccess, timestamp, envelopeContent, false, null, null, message.isUrgent(), false);
|
||||
}
|
||||
|
||||
public List<SendMessageResult> sendCallMessage(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
SignalServiceCallMessage message)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -378,12 +379,13 @@ public class SignalServiceMessageSender {
|
|||
Content content = createCallContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.DEFAULT, Optional.empty());
|
||||
|
||||
return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, null, message.isUrgent(), false);
|
||||
return sendMessage(recipients, sealedSenderAccesses, timestamp, envelopeContent, false, null, null, null, message.isUrgent(), false);
|
||||
}
|
||||
|
||||
public List<SendMessageResult> sendCallMessage(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
SignalServiceCallMessage message,
|
||||
PartialSendBatchCompleteListener partialListener)
|
||||
throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException
|
||||
|
@ -392,7 +394,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
Content content = createCallContent(message);
|
||||
|
||||
List<SendMessageResult> results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp().get(), content, ContentHint.IMPLICIT, message.getGroupId(), false, SenderKeyGroupEvents.EMPTY, message.isUrgent(), false);
|
||||
List<SendMessageResult> results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, groupSendEndorsements, message.getTimestamp().get(), content, ContentHint.IMPLICIT, message.getGroupId(), false, SenderKeyGroupEvents.EMPTY, message.isUrgent(), false);
|
||||
|
||||
if (partialListener != null) {
|
||||
partialListener.onPartialSendComplete(results);
|
||||
|
@ -423,27 +425,27 @@ public class SignalServiceMessageSender {
|
|||
* @throws UntrustedIdentityException
|
||||
* @throws IOException
|
||||
*/
|
||||
public SendMessageResult sendDataMessage(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
IndividualSendEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean includePniSignature)
|
||||
public SendMessageResult sendDataMessage(SignalServiceAddress recipient,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
IndividualSendEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean includePniSignature)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message.");
|
||||
|
||||
Content content = createMessageContent(message);
|
||||
|
||||
return sendContent(recipient, unidentifiedAccess, contentHint, message, sendEvents, urgent, includePniSignature, content);
|
||||
return sendContent(recipient, sealedSenderAccess, contentHint, message, sendEvents, urgent, includePniSignature, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an edit message to a single recipient.
|
||||
*/
|
||||
public SendMessageResult sendEditMessage(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
IndividualSendEvents sendEvents,
|
||||
|
@ -455,14 +457,14 @@ public class SignalServiceMessageSender {
|
|||
|
||||
Content content = createEditMessageContent(new SignalServiceEditMessage(targetSentTimestamp, message));
|
||||
|
||||
return sendContent(recipient, unidentifiedAccess, contentHint, message, sendEvents, urgent, false, content);
|
||||
return sendContent(recipient, sealedSenderAccess, contentHint, message, sendEvents, urgent, false, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends content to a single recipient.
|
||||
*/
|
||||
private SendMessageResult sendContent(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
IndividualSendEvents sendEvents,
|
||||
|
@ -481,7 +483,7 @@ public class SignalServiceMessageSender {
|
|||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
|
||||
long timestamp = message.getTimestamp();
|
||||
SendMessageResult result = sendMessage(recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, sendEvents, urgent, false);
|
||||
SendMessageResult result = sendMessage(recipient, sealedSenderAccess, timestamp, envelopeContent, false, null, sendEvents, urgent, false);
|
||||
|
||||
sendEvents.onMessageSent();
|
||||
|
||||
|
@ -489,7 +491,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, null, false, false);
|
||||
sendMessage(localAddress, SealedSenderAccess.NONE, timestamp, syncMessageContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
sendEvents.onSyncMessageSent();
|
||||
|
@ -509,13 +511,13 @@ public class SignalServiceMessageSender {
|
|||
/**
|
||||
* Sends the provided {@link SenderKeyDistributionMessage} to the specified recipients.
|
||||
*/
|
||||
public List<SendMessageResult> sendSenderKeyDistributionMessage(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
SenderKeyDistributionMessage message,
|
||||
Optional<byte[]> groupId,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
public List<SendMessageResult> sendSenderKeyDistributionMessage(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
SenderKeyDistributionMessage message,
|
||||
Optional<byte[]> groupId,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
throws IOException
|
||||
{
|
||||
ByteString distributionBytes = ByteString.of(message.serialize());
|
||||
|
@ -525,14 +527,14 @@ public class SignalServiceMessageSender {
|
|||
|
||||
Log.d(TAG, "[" + timestamp + "] Sending SKDM to " + recipients.size() + " recipients for DistributionId " + distributionId);
|
||||
|
||||
return sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, null, null, null, urgent, story);
|
||||
return sendMessage(recipients, sealedSenderAccesses, timestamp, envelopeContent, false, null, null, null, urgent, story);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend a previously-sent message.
|
||||
*/
|
||||
public SendMessageResult resendContent(SignalServiceAddress address,
|
||||
Optional<UnidentifiedAccessPair> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
long timestamp,
|
||||
Content content,
|
||||
ContentHint contentHint,
|
||||
|
@ -543,13 +545,12 @@ public class SignalServiceMessageSender {
|
|||
Log.d(TAG, "[" + timestamp + "] Resending content.");
|
||||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, groupId);
|
||||
Optional<UnidentifiedAccess> access = unidentifiedAccess.isPresent() ? unidentifiedAccess.get().getTargetUnidentifiedAccess() : Optional.empty();
|
||||
|
||||
if (address.getServiceId().equals(localAddress.getServiceId())) {
|
||||
access = Optional.empty();
|
||||
sealedSenderAccess = SealedSenderAccess.NONE;
|
||||
}
|
||||
|
||||
return sendMessage(address, access, timestamp, envelopeContent, false, null, null, urgent, false);
|
||||
return sendMessage(address, sealedSenderAccess, timestamp, envelopeContent, false, null, null, urgent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -558,6 +559,7 @@ public class SignalServiceMessageSender {
|
|||
public List<SendMessageResult> sendGroupDataMessage(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
|
@ -579,7 +581,7 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
Optional<byte[]> groupId = message.getGroupId();
|
||||
List<SendMessageResult> results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, message.getTimestamp(), content, contentHint, groupId, false, sendEvents, urgent, isForStory);
|
||||
List<SendMessageResult> results = sendGroupMessage(distributionId, recipients, unidentifiedAccess, groupSendEndorsements, message.getTimestamp(), content, contentHint, groupId, false, sendEvents, urgent, isForStory);
|
||||
|
||||
if (partialListener != null) {
|
||||
partialListener.onPartialSendComplete(results);
|
||||
|
@ -591,7 +593,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, null, false, false);
|
||||
sendMessage(localAddress, SealedSenderAccess.NONE, message.getTimestamp(), syncMessageContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
sendEvents.onSyncMessageSent();
|
||||
|
@ -605,15 +607,15 @@ public class SignalServiceMessageSender {
|
|||
* @param partialListener A listener that will be called when an individual send is completed. Will be invoked on an arbitrary background thread, *not*
|
||||
* the calling thread.
|
||||
*/
|
||||
public List<SendMessageResult> sendDataMessage(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
LegacyGroupEvents sendEvents,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal,
|
||||
boolean urgent)
|
||||
public List<SendMessageResult> sendDataMessage(List<SignalServiceAddress> recipients,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
LegacyGroupEvents sendEvents,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal,
|
||||
boolean urgent)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a data message to " + recipients.size() + " recipients.");
|
||||
|
@ -621,7 +623,7 @@ public class SignalServiceMessageSender {
|
|||
Content content = createMessageContent(message);
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
long timestamp = message.getTimestamp();
|
||||
List<SendMessageResult> results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, partialListener, cancelationSignal, sendEvents, urgent, false);
|
||||
List<SendMessageResult> results = sendMessage(recipients, sealedSenderAccesses, timestamp, envelopeContent, false, partialListener, cancelationSignal, sendEvents, urgent, false);
|
||||
boolean needsSyncInResults = false;
|
||||
|
||||
sendEvents.onMessageSent();
|
||||
|
@ -642,7 +644,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, null, false, false);
|
||||
sendMessage(localAddress, SealedSenderAccess.NONE, timestamp, syncMessageContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
sendEvents.onSyncMessageSent();
|
||||
|
@ -656,16 +658,16 @@ public class SignalServiceMessageSender {
|
|||
* @param partialListener A listener that will be called when an individual send is completed. Will be invoked on an arbitrary background thread, *not*
|
||||
* the calling thread.
|
||||
*/
|
||||
public List<SendMessageResult> sendEditMessage(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccessPair>> unidentifiedAccess,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
LegacyGroupEvents sendEvents,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal,
|
||||
boolean urgent,
|
||||
long targetSentTimestamp)
|
||||
public List<SendMessageResult> sendEditMessage(List<SignalServiceAddress> recipients,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
SignalServiceDataMessage message,
|
||||
LegacyGroupEvents sendEvents,
|
||||
PartialSendCompleteListener partialListener,
|
||||
CancelationSignal cancelationSignal,
|
||||
boolean urgent,
|
||||
long targetSentTimestamp)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Log.d(TAG, "[" + message.getTimestamp() + "] Sending a edit message to " + recipients.size() + " recipients.");
|
||||
|
@ -673,7 +675,7 @@ public class SignalServiceMessageSender {
|
|||
Content content = createEditMessageContent(new SignalServiceEditMessage(targetSentTimestamp, message));
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, contentHint, message.getGroupId());
|
||||
long timestamp = message.getTimestamp();
|
||||
List<SendMessageResult> results = sendMessage(recipients, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, envelopeContent, false, partialListener, cancelationSignal, null, urgent, false);
|
||||
List<SendMessageResult> results = sendMessage(recipients, sealedSenderAccesses, timestamp, envelopeContent, false, partialListener, cancelationSignal, null, urgent, false);
|
||||
boolean needsSyncInResults = false;
|
||||
|
||||
sendEvents.onMessageSent();
|
||||
|
@ -694,7 +696,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, null, false, false);
|
||||
sendMessage(localAddress, SealedSenderAccess.NONE, timestamp, syncMessageContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
sendEvents.onSyncMessageSent();
|
||||
|
@ -706,17 +708,17 @@ public class SignalServiceMessageSender {
|
|||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Log.d(TAG, "[" + dataMessage.getTimestamp() + "] Sending self-sync message.");
|
||||
return sendSyncMessage(createSelfSendSyncMessage(dataMessage), Optional.empty());
|
||||
return sendSyncMessage(createSelfSendSyncMessage(dataMessage));
|
||||
}
|
||||
|
||||
public SendMessageResult sendSelfSyncEditMessage(SignalServiceEditMessage editMessage)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Log.d(TAG, "[" + editMessage.getDataMessage().getTimestamp() + "] Sending self-sync edit message for " + editMessage.getTargetSentTimestamp() + ".");
|
||||
return sendSyncMessage(createSelfSendSyncEditMessage(editMessage), Optional.empty());
|
||||
return sendSyncMessage(createSelfSendSyncEditMessage(editMessage));
|
||||
}
|
||||
|
||||
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message, Optional<UnidentifiedAccessPair> unidentifiedAccess)
|
||||
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
Content content;
|
||||
|
@ -736,7 +738,7 @@ public class SignalServiceMessageSender {
|
|||
} else if (message.getConfiguration().isPresent()) {
|
||||
content = createMultiDeviceConfigurationContent(message.getConfiguration().get());
|
||||
} else if (message.getSent().isPresent()) {
|
||||
content = createMultiDeviceSentTranscriptContent(message.getSent().get(), unidentifiedAccess.isPresent());
|
||||
content = createMultiDeviceSentTranscriptContent(message.getSent().get());
|
||||
} else if (message.getStickerPackOperations().isPresent()) {
|
||||
content = createMultiDeviceStickerPackOperationContent(message.getStickerPackOperations().get());
|
||||
} else if (message.getFetchType().isPresent()) {
|
||||
|
@ -774,7 +776,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
return sendMessage(localAddress, Optional.empty(), timestamp, envelopeContent, false, null, null, urgent, false);
|
||||
return sendMessage(localAddress, SealedSenderAccess.NONE, timestamp, envelopeContent, false, null, null, urgent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -792,7 +794,7 @@ public class SignalServiceMessageSender {
|
|||
Content.Builder content = new Content.Builder().syncMessage(syncMessage.build());
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content.build(), ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
return getEncryptedMessage(localAddress, Optional.empty(), deviceId, envelopeContent, false);
|
||||
return getEncryptedMessage(localAddress, SealedSenderAccess.NONE, deviceId, envelopeContent, false);
|
||||
}
|
||||
|
||||
public void cancelInFlightRequests() {
|
||||
|
@ -927,19 +929,19 @@ public class SignalServiceMessageSender {
|
|||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
SendMessageResult result = sendMessage(message.getDestination(), Optional.empty(), message.getTimestamp(), envelopeContent, false, null, null, false, false);
|
||||
SendMessageResult result = sendMessage(message.getDestination(), null, message.getTimestamp(), envelopeContent, false, null, null, false, false);
|
||||
|
||||
if (result.getSuccess().isNeedsSync()) {
|
||||
Content syncMessage = createMultiDeviceVerifiedContent(message, nullMessage.encode());
|
||||
EnvelopeContent syncMessageContent = EnvelopeContent.encrypted(syncMessage, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
sendMessage(localAddress, Optional.empty(), message.getTimestamp(), syncMessageContent, false, null, null, false, false);
|
||||
sendMessage(localAddress, SealedSenderAccess.NONE, message.getTimestamp(), syncMessageContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public SendMessageResult sendNullMessage(SignalServiceAddress address, Optional<UnidentifiedAccessPair> unidentifiedAccess)
|
||||
public SendMessageResult sendNullMessage(SignalServiceAddress address, @Nullable SealedSenderAccess sealedSenderAccess)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
byte[] nullMessageBody = new DataMessage.Builder()
|
||||
|
@ -957,7 +959,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
EnvelopeContent envelopeContent = EnvelopeContent.encrypted(content, ContentHint.IMPLICIT, Optional.empty());
|
||||
|
||||
return sendMessage(address, getTargetUnidentifiedAccess(unidentifiedAccess), System.currentTimeMillis(), envelopeContent, false, null, null, false, false);
|
||||
return sendMessage(address, sealedSenderAccess, System.currentTimeMillis(), envelopeContent, false, null, null, false, false);
|
||||
}
|
||||
|
||||
private PniSignatureMessage createPniSignatureMessage() {
|
||||
|
@ -1380,10 +1382,10 @@ public class SignalServiceMessageSender {
|
|||
return container.syncMessage(builder.build()).build();
|
||||
}
|
||||
|
||||
private Content createMultiDeviceSentTranscriptContent(SentTranscriptMessage transcript, boolean unidentifiedAccess) throws IOException {
|
||||
private Content createMultiDeviceSentTranscriptContent(SentTranscriptMessage transcript) throws IOException {
|
||||
SignalServiceAddress address = transcript.getDestination().get();
|
||||
Content content = createMessageContent(transcript);
|
||||
SendMessageResult result = SendMessageResult.success(address, Collections.emptyList(), unidentifiedAccess, true, -1, Optional.ofNullable(content));
|
||||
SendMessageResult result = SendMessageResult.success(address, Collections.emptyList(), false, true, -1, Optional.ofNullable(content));
|
||||
|
||||
|
||||
return createMultiDeviceSentTranscriptContent(content,
|
||||
|
@ -1424,7 +1426,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
unidentifiedDeliveryStatuses.add(new SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder()
|
||||
.destinationServiceId(result.getAddress().getServiceId().toString())
|
||||
.unidentified(result.getSuccess().isUnidentified())
|
||||
.unidentified(false)
|
||||
.destinationIdentityKey(identity)
|
||||
.build());
|
||||
}
|
||||
|
@ -1933,15 +1935,15 @@ public class SignalServiceMessageSender {
|
|||
return SignalServiceSyncMessage.forSentTranscript(transcript);
|
||||
}
|
||||
|
||||
private SendMessageResult sendMessage(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
long timestamp,
|
||||
EnvelopeContent content,
|
||||
boolean online,
|
||||
CancelationSignal cancelationSignal,
|
||||
SendEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
private SendMessageResult sendMessage(SignalServiceAddress recipient,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
long timestamp,
|
||||
EnvelopeContent content,
|
||||
boolean online,
|
||||
CancelationSignal cancelationSignal,
|
||||
SendEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
throws UntrustedIdentityException, IOException
|
||||
{
|
||||
enforceMaxContentSize(content);
|
||||
|
@ -1954,7 +1956,13 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
try {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(recipient, unidentifiedAccess, timestamp, content, online, urgent, story);
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(recipient,
|
||||
sealedSenderAccess,
|
||||
timestamp,
|
||||
content,
|
||||
online,
|
||||
urgent,
|
||||
story);
|
||||
if (i == 0 && sendEvents != null) {
|
||||
sendEvents.onMessageEncrypted();
|
||||
}
|
||||
|
@ -1969,52 +1977,41 @@ public class SignalServiceMessageSender {
|
|||
throw new CancelationException();
|
||||
}
|
||||
|
||||
if (!unidentifiedAccess.isPresent()) {
|
||||
try {
|
||||
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
|
||||
throw e;
|
||||
} catch (WebSocketUnavailableException e) {
|
||||
Log.i(TAG, "[sendMessage][" + timestamp + "] Pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
Log.w(TAG, "[sendMessage][" + timestamp + "] Pipe failed, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
|
||||
}
|
||||
} else if (unidentifiedAccess.isPresent()) {
|
||||
try {
|
||||
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
|
||||
throw e;
|
||||
} catch (WebSocketUnavailableException e) {
|
||||
Log.i(TAG, "[sendMessage][" + timestamp + "] Unidentified pipe unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
Throwable cause = e;
|
||||
if (e.getCause() != null) {
|
||||
cause = e.getCause();
|
||||
}
|
||||
Log.w(TAG, "[sendMessage][" + timestamp + "] Unidentified pipe failed, falling back... (" + cause.getClass().getSimpleName() + ": " + cause.getMessage() + ")");
|
||||
try {
|
||||
SendMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.send(messages, sealedSenderAccess, 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
|
||||
throw e;
|
||||
} catch (WebSocketUnavailableException e) {
|
||||
String pipe = sealedSenderAccess == null ? "Pipe" : "Unidentified pipe";
|
||||
Log.i(TAG, "[sendMessage][" + timestamp + "] " + pipe + " unavailable, falling back... (" + e.getClass().getSimpleName() + ": " + e.getMessage() + ")");
|
||||
} catch (IOException e) {
|
||||
String pipe = sealedSenderAccess == null ? "Pipe" : "Unidentified pipe";
|
||||
Throwable cause = e;
|
||||
if (e.getCause() != null) {
|
||||
cause = e.getCause();
|
||||
}
|
||||
Log.w(TAG, "[sendMessage][" + timestamp + "] " + pipe + " failed, falling back... (" + cause.getClass().getSimpleName() + ": " + cause.getMessage() + ")");
|
||||
}
|
||||
|
||||
if (cancelationSignal != null && cancelationSignal.isCanceled()) {
|
||||
throw new CancelationException();
|
||||
}
|
||||
|
||||
SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess, story);
|
||||
SendMessageResponse response = socket.sendMessage(messages, sealedSenderAccess, story);
|
||||
|
||||
return SendMessageResult.success(recipient, messages.getDevices(), response.sentUnidentified(), response.getNeedsSync() || aciStore.isMultiDevice(), System.currentTimeMillis() - startTime, content.getContent());
|
||||
|
||||
} catch (InvalidKeyException ike) {
|
||||
Log.w(TAG, ike);
|
||||
unidentifiedAccess = Optional.empty();
|
||||
if (sealedSenderAccess != null) {
|
||||
sealedSenderAccess = sealedSenderAccess.switchToFallback();
|
||||
}
|
||||
} catch (AuthorizationFailedException afe) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
if (sealedSenderAccess != null) {
|
||||
Log.w(TAG, "Got an AuthorizationFailedException when trying to send using sealed sender. Falling back.");
|
||||
unidentifiedAccess = Optional.empty();
|
||||
sealedSenderAccess = sealedSenderAccess.switchToFallback();
|
||||
} else {
|
||||
Log.w(TAG, "Got an AuthorizationFailedException without using sealed sender!", afe);
|
||||
throw afe;
|
||||
|
@ -2038,7 +2035,7 @@ public class SignalServiceMessageSender {
|
|||
* @throws IOException - Unknown failure or a failure not representable by an unsuccessful {@code SendMessageResult}.
|
||||
*/
|
||||
private List<SendMessageResult> sendMessage(List<SignalServiceAddress> recipients,
|
||||
List<Optional<UnidentifiedAccess>> unidentifiedAccess,
|
||||
List<SealedSenderAccess> sealedSenderAccesses,
|
||||
long timestamp,
|
||||
EnvelopeContent content,
|
||||
boolean online,
|
||||
|
@ -2052,15 +2049,16 @@ public class SignalServiceMessageSender {
|
|||
Log.d(TAG, "[" + timestamp + "] Sending to " + recipients.size() + " recipients.");
|
||||
enforceMaxContentSize(content);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<Observable<SendMessageResult>> singleResults = new LinkedList<>();
|
||||
Iterator<SignalServiceAddress> recipientIterator = recipients.iterator();
|
||||
Iterator<Optional<UnidentifiedAccess>> unidentifiedAccessIterator = unidentifiedAccess.iterator();
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<Observable<SendMessageResult>> singleResults = new LinkedList<>();
|
||||
Iterator<SignalServiceAddress> recipientIterator = recipients.iterator();
|
||||
Iterator<SealedSenderAccess> sealedSenderAccessIterator = sealedSenderAccesses.iterator();
|
||||
|
||||
while (recipientIterator.hasNext()) {
|
||||
SignalServiceAddress recipient = recipientIterator.next();
|
||||
Optional<UnidentifiedAccess> access = unidentifiedAccessIterator.next();
|
||||
singleResults.add(sendMessageRx(recipient, access, timestamp, content, online, cancelationSignal, sendEvents, urgent, story, 0).toObservable());
|
||||
SignalServiceAddress recipient = recipientIterator.next();
|
||||
SealedSenderAccess sealedSenderAccess = sealedSenderAccessIterator.next();
|
||||
|
||||
singleResults.add(sendMessageRx(recipient, sealedSenderAccess, timestamp, content, online, cancelationSignal, sendEvents, urgent, story, 0).toObservable());
|
||||
}
|
||||
|
||||
List<SendMessageResult> results;
|
||||
|
@ -2126,7 +2124,7 @@ public class SignalServiceMessageSender {
|
|||
* errors via {@code onError}
|
||||
*/
|
||||
private Single<SendMessageResult> sendMessageRx(SignalServiceAddress recipient,
|
||||
final Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
long timestamp,
|
||||
EnvelopeContent content,
|
||||
boolean online,
|
||||
|
@ -2140,7 +2138,7 @@ public class SignalServiceMessageSender {
|
|||
enforceMaxContentSize(content);
|
||||
|
||||
Single<OutgoingPushMessageList> messagesSingle = Single.fromCallable(() -> {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(recipient, unidentifiedAccess, timestamp, content, online, urgent, story);
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(recipient, sealedSenderAccess, timestamp, content, online, urgent, story);
|
||||
|
||||
if (retryCount == 0 && sendEvents != null) {
|
||||
sendEvents.onMessageEncrypted();
|
||||
|
@ -2161,7 +2159,7 @@ public class SignalServiceMessageSender {
|
|||
return Single.error(new CancelationException());
|
||||
}
|
||||
|
||||
return messagingService.send(messages, unidentifiedAccess, story)
|
||||
return messagingService.send(messages, sealedSenderAccess, story)
|
||||
.map(r -> new kotlin.Pair<>(messages, r));
|
||||
})
|
||||
.observeOn(scheduler)
|
||||
|
@ -2196,14 +2194,14 @@ public class SignalServiceMessageSender {
|
|||
// Non-technical failures shouldn't be retried with socket
|
||||
return Single.error(throwable);
|
||||
} else if (throwable instanceof WebSocketUnavailableException) {
|
||||
Log.i(TAG, "[sendMessage][" + timestamp + "] " + (unidentifiedAccess.isPresent() ? "Unidentified " : "") + "pipe unavailable, falling back... (" + throwable.getClass().getSimpleName() + ": " + throwable.getMessage() + ")");
|
||||
Log.i(TAG, "[sendMessage][" + timestamp + "] " + (sealedSenderAccess != null ? "Unidentified " : "") + "pipe unavailable, falling back... (" + throwable.getClass().getSimpleName() + ": " + throwable.getMessage() + ")");
|
||||
} else if (throwable instanceof IOException) {
|
||||
Throwable cause = throwable.getCause() != null ? throwable.getCause() : throwable;
|
||||
Log.w(TAG, "[sendMessage][" + timestamp + "] " + (unidentifiedAccess.isPresent() ? "Unidentified " : "") + "pipe failed, falling back... (" + cause.getClass().getSimpleName() + ": " + cause.getMessage() + ")");
|
||||
Log.w(TAG, "[sendMessage][" + timestamp + "] " + (sealedSenderAccess != null ? "Unidentified " : "") + "pipe failed, falling back... (" + cause.getClass().getSimpleName() + ": " + cause.getMessage() + ")");
|
||||
}
|
||||
|
||||
return Single.fromCallable(() -> {
|
||||
SendMessageResponse response = socket.sendMessage(messages, unidentifiedAccess, story);
|
||||
SendMessageResponse response = socket.sendMessage(messages, sealedSenderAccess, story);
|
||||
return SendMessageResult.success(
|
||||
recipient,
|
||||
messages.getDevices(),
|
||||
|
@ -2229,7 +2227,7 @@ public class SignalServiceMessageSender {
|
|||
Log.w(TAG, t);
|
||||
return sendMessageRx(
|
||||
recipient,
|
||||
Optional.empty(),
|
||||
SealedSenderAccess.NONE,
|
||||
timestamp,
|
||||
content,
|
||||
online,
|
||||
|
@ -2240,11 +2238,11 @@ public class SignalServiceMessageSender {
|
|||
retryCount + 1
|
||||
);
|
||||
} else if (t instanceof AuthorizationFailedException) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
if (sealedSenderAccess != null) {
|
||||
Log.w(TAG, "Got an AuthorizationFailedException when trying to send using sealed sender. Falling back.");
|
||||
return sendMessageRx(
|
||||
recipient,
|
||||
Optional.empty(),
|
||||
sealedSenderAccess.switchToFallback(),
|
||||
timestamp,
|
||||
content,
|
||||
online,
|
||||
|
@ -2268,7 +2266,7 @@ public class SignalServiceMessageSender {
|
|||
})
|
||||
.flatMap(unused -> sendMessageRx(
|
||||
recipient,
|
||||
unidentifiedAccess,
|
||||
sealedSenderAccess,
|
||||
timestamp,
|
||||
content,
|
||||
online,
|
||||
|
@ -2288,7 +2286,7 @@ public class SignalServiceMessageSender {
|
|||
})
|
||||
.flatMap(unused -> sendMessageRx(
|
||||
recipient,
|
||||
unidentifiedAccess,
|
||||
sealedSenderAccess,
|
||||
timestamp,
|
||||
content,
|
||||
online,
|
||||
|
@ -2336,17 +2334,18 @@ public class SignalServiceMessageSender {
|
|||
*
|
||||
* This method will handle sending out SenderKeyDistributionMessages as necessary.
|
||||
*/
|
||||
private List<SendMessageResult> sendGroupMessage(DistributionId distributionId,
|
||||
private List<SendMessageResult> sendGroupMessage(DistributionId distributionId,
|
||||
List<SignalServiceAddress> recipients,
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
long timestamp,
|
||||
Content content,
|
||||
ContentHint contentHint,
|
||||
Optional<byte[]> groupId,
|
||||
boolean online,
|
||||
SenderKeyGroupEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
List<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable GroupSendEndorsements groupSendEndorsements,
|
||||
long timestamp,
|
||||
Content content,
|
||||
ContentHint contentHint,
|
||||
Optional<byte[]> groupId,
|
||||
boolean online,
|
||||
SenderKeyGroupEvents sendEvents,
|
||||
boolean urgent,
|
||||
boolean story)
|
||||
throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException
|
||||
{
|
||||
if (recipients.isEmpty()) {
|
||||
|
@ -2364,34 +2363,36 @@ public class SignalServiceMessageSender {
|
|||
accessBySid.put(addressIterator.next().getServiceId(), accessIterator.next());
|
||||
}
|
||||
|
||||
SealedSenderAccess sealedSenderAccess = SealedSenderAccess.forGroupSend(groupSendEndorsements, unidentifiedAccess, story);
|
||||
|
||||
for (int i = 0; i < RETRY_COUNT; i++) {
|
||||
GroupTargetInfo targetInfo = buildGroupTargetInfo(recipients);
|
||||
final GroupTargetInfo targetInfoSnapshot = targetInfo;
|
||||
|
||||
Set<SignalProtocolAddress> sharedWith = aciStore.getSenderKeySharedWith(distributionId);
|
||||
List<SignalServiceAddress> needsSenderKey = targetInfo.destinations.stream()
|
||||
.filter(a -> !sharedWith.contains(a) || targetInfoSnapshot.sessions.get(a) == null)
|
||||
.map(a -> ServiceId.parseOrThrow(a.getName()))
|
||||
.distinct()
|
||||
.map(SignalServiceAddress::new)
|
||||
.collect(Collectors.toList());
|
||||
if (needsSenderKey.size() > 0) {
|
||||
Log.i(TAG, "[sendGroupMessage][" + timestamp + "] Need to send the distribution message to " + needsSenderKey.size() + " addresses.");
|
||||
SenderKeyDistributionMessage message = getOrCreateNewGroupSession(distributionId);
|
||||
List<Optional<UnidentifiedAccessPair>> access = needsSenderKey.stream()
|
||||
.map(r -> {
|
||||
UnidentifiedAccess targetAccess = accessBySid.get(r.getServiceId());
|
||||
return Optional.of(new UnidentifiedAccessPair(targetAccess, targetAccess));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
Set<SignalProtocolAddress> sharedWith = aciStore.getSenderKeySharedWith(distributionId);
|
||||
List<SignalServiceAddress> needsSenderKeyTargets = targetInfo.destinations.stream()
|
||||
.filter(a -> !sharedWith.contains(a) || targetInfoSnapshot.sessions.get(a) == null)
|
||||
.map(a -> ServiceId.parseOrThrow(a.getName()))
|
||||
.distinct()
|
||||
.map(SignalServiceAddress::new)
|
||||
.collect(Collectors.toList());
|
||||
if (needsSenderKeyTargets.size() > 0) {
|
||||
Log.i(TAG, "[sendGroupMessage][" + timestamp + "] Need to send the distribution message to " + needsSenderKeyTargets.size() + " addresses.");
|
||||
SenderKeyDistributionMessage senderKeyDistributionMessage = getOrCreateNewGroupSession(distributionId);
|
||||
List<UnidentifiedAccess> needsSenderKeyAccesses = needsSenderKeyTargets.stream()
|
||||
.map(r -> accessBySid.get(r.getServiceId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<GroupSendFullToken> needsSenderKeyGroupSendTokens = groupSendEndorsements != null ? groupSendEndorsements.forIndividuals(needsSenderKeyTargets) : null;
|
||||
List<SealedSenderAccess> needsSenderKeySealedSenderAccesses = SealedSenderAccess.forFanOutGroupSend(needsSenderKeyGroupSendTokens, sealedSenderAccess.getSenderCertificate(), needsSenderKeyAccesses);
|
||||
|
||||
List<SendMessageResult> results = sendSenderKeyDistributionMessage(distributionId,
|
||||
needsSenderKey,
|
||||
access,
|
||||
message,
|
||||
needsSenderKeyTargets,
|
||||
needsSenderKeySealedSenderAccesses,
|
||||
senderKeyDistributionMessage,
|
||||
groupId,
|
||||
urgent,
|
||||
story && !groupId.isPresent()); // We don't want to flag SKDM's as stories for group stories, since we reuse distributionIds for normal group messages
|
||||
story && groupId.isEmpty()); // We don't want to flag SKDM's as stories for group stories, since we reuse distributionIds for normal group messages
|
||||
|
||||
List<SignalServiceAddress> successes = results.stream()
|
||||
.filter(SendMessageResult::isSuccess)
|
||||
|
@ -2403,7 +2404,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
aciStore.markSenderKeySharedWith(distributionId, successAddresses);
|
||||
|
||||
Log.i(TAG, "[sendGroupMessage][" + timestamp + "] Successfully sent sender keys to " + successes.size() + "/" + needsSenderKey.size() + " recipients.");
|
||||
Log.i(TAG, "[sendGroupMessage][" + timestamp + "] Successfully sent sender keys to " + successes.size() + "/" + needsSenderKeyTargets.size() + " recipients.");
|
||||
|
||||
int failureCount = results.size() - successes.size();
|
||||
if (failureCount > 0) {
|
||||
|
@ -2434,26 +2435,20 @@ public class SignalServiceMessageSender {
|
|||
|
||||
sendEvents.onSenderKeyShared();
|
||||
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, aciStore, sessionLock, null);
|
||||
SenderCertificate senderCertificate = unidentifiedAccess.get(0).getUnidentifiedCertificate();
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, localDeviceId, aciStore, sessionLock, null);
|
||||
|
||||
byte[] ciphertext;
|
||||
try {
|
||||
ciphertext = cipher.encryptForGroup(distributionId, targetInfo.destinations, targetInfo.sessions, senderCertificate, content.encode(), contentHint, groupId);
|
||||
ciphertext = cipher.encryptForGroup(distributionId, targetInfo.destinations, targetInfo.sessions, sealedSenderAccess.getSenderCertificate(), content.encode(), contentHint, groupId);
|
||||
} catch (org.signal.libsignal.protocol.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted during group encrypt", e.getName(), e.getUntrustedIdentity());
|
||||
}
|
||||
|
||||
sendEvents.onMessageEncrypted();
|
||||
|
||||
byte[] joinedUnidentifiedAccess = new byte[16];
|
||||
for (UnidentifiedAccess access : unidentifiedAccess) {
|
||||
joinedUnidentifiedAccess = ByteArrayUtil.xor(joinedUnidentifiedAccess, access.getUnidentifiedAccessKey());
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
SendGroupMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.sendToGroup(ciphertext, joinedUnidentifiedAccess, timestamp, online, urgent, story).blockingGet()).getResultOrThrow();
|
||||
SendGroupMessageResponse response = new MessagingService.SendResponseProcessor<>(messagingService.sendToGroup(ciphertext, sealedSenderAccess, 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
|
||||
|
@ -2464,7 +2459,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, story);
|
||||
SendGroupMessageResponse response = socket.sendGroupMessage(ciphertext, sealedSenderAccess, timestamp, online, urgent, story);
|
||||
return transformGroupResponseToMessageResults(targetInfo.devices, response, content);
|
||||
} catch (GroupMismatchedDevicesException e) {
|
||||
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Handling mismatched devices. (" + e.getMessage() + ")");
|
||||
|
@ -2478,6 +2473,13 @@ public class SignalServiceMessageSender {
|
|||
SignalServiceAddress address = new SignalServiceAddress(ServiceId.parseOrThrow(stale.getUuid()), Optional.empty());
|
||||
handleStaleDevices(address, stale.getDevices());
|
||||
}
|
||||
} catch (InvalidUnidentifiedAccessHeaderException e) {
|
||||
sealedSenderAccess = sealedSenderAccess.switchToFallback();
|
||||
if (sealedSenderAccess != null) {
|
||||
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Handling invalid group send endorsements. (" + e.getMessage() + ")");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Attempt failed (i = " + i + ")");
|
||||
|
@ -2637,7 +2639,7 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
private OutgoingPushMessageList getEncryptedMessages(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
long timestamp,
|
||||
EnvelopeContent plaintext,
|
||||
boolean online,
|
||||
|
@ -2659,7 +2661,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
for (int deviceId : deviceIds) {
|
||||
if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || aciStore.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) {
|
||||
messages.add(getEncryptedMessage(recipient, unidentifiedAccess, deviceId, plaintext, story));
|
||||
messages.add(getEncryptedMessage(recipient, sealedSenderAccess, deviceId, plaintext, story));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2668,7 +2670,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
// Visible for testing only
|
||||
public OutgoingPushMessage getEncryptedMessage(SignalServiceAddress recipient,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
int deviceId,
|
||||
EnvelopeContent plaintext,
|
||||
boolean story)
|
||||
|
@ -2679,7 +2681,7 @@ public class SignalServiceMessageSender {
|
|||
|
||||
if (!aciStore.containsSession(signalProtocolAddress)) {
|
||||
try {
|
||||
List<PreKeyBundle> preKeys = getPreKeys(recipient, unidentifiedAccess, deviceId, story);
|
||||
List<PreKeyBundle> preKeys = getPreKeys(recipient, sealedSenderAccess, deviceId, story);
|
||||
|
||||
for (PreKeyBundle preKey : preKeys) {
|
||||
Log.d(TAG, "Initializing prekey session for " + signalProtocolAddress);
|
||||
|
@ -2702,24 +2704,24 @@ public class SignalServiceMessageSender {
|
|||
}
|
||||
|
||||
try {
|
||||
return cipher.encrypt(signalProtocolAddress, unidentifiedAccess, plaintext);
|
||||
return cipher.encrypt(signalProtocolAddress, sealedSenderAccess, plaintext);
|
||||
} catch (org.signal.libsignal.protocol.UntrustedIdentityException e) {
|
||||
throw new UntrustedIdentityException("Untrusted on send", recipient.getIdentifier(), e.getUntrustedIdentity());
|
||||
}
|
||||
}
|
||||
|
||||
private List<PreKeyBundle> getPreKeys(SignalServiceAddress recipient, Optional<UnidentifiedAccess> unidentifiedAccess, int deviceId, boolean story) throws IOException {
|
||||
private List<PreKeyBundle> getPreKeys(SignalServiceAddress recipient, @Nullable SealedSenderAccess sealedSenderAccess, int deviceId, boolean story) throws IOException {
|
||||
try {
|
||||
// If it's only unrestricted because it's a story send, then we know it'll fail
|
||||
if (story && unidentifiedAccess.isPresent() && unidentifiedAccess.get().isUnrestrictedForStory()) {
|
||||
unidentifiedAccess = Optional.empty();
|
||||
if (story && SealedSenderAccess.isUnrestrictedForStory(sealedSenderAccess)) {
|
||||
sealedSenderAccess = null;
|
||||
}
|
||||
|
||||
return socket.getPreKeys(recipient, unidentifiedAccess, deviceId);
|
||||
return socket.getPreKeys(recipient, sealedSenderAccess, deviceId);
|
||||
} catch (NonSuccessfulResponseCodeException e) {
|
||||
if (e.getCode() == 401 && story) {
|
||||
Log.d(TAG, "Got 401 when fetching prekey for story. Trying without UD.");
|
||||
return socket.getPreKeys(recipient, Optional.empty(), deviceId);
|
||||
return socket.getPreKeys(recipient, null, deviceId);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
@ -2782,25 +2784,6 @@ public class SignalServiceMessageSender {
|
|||
return addresses;
|
||||
}
|
||||
|
||||
private Optional<UnidentifiedAccess> getTargetUnidentifiedAccess(Optional<UnidentifiedAccessPair> unidentifiedAccess) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
return unidentifiedAccess.get().getTargetUnidentifiedAccess();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private List<Optional<UnidentifiedAccess>> getTargetUnidentifiedAccess(List<Optional<UnidentifiedAccessPair>> unidentifiedAccess) {
|
||||
List<Optional<UnidentifiedAccess>> results = new LinkedList<>();
|
||||
|
||||
for (Optional<UnidentifiedAccessPair> item : unidentifiedAccess) {
|
||||
if (item.isPresent()) results.add(item.get().getTargetUnidentifiedAccess());
|
||||
else results.add(Optional.empty());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private EnvelopeContent enforceMaxContentSize(EnvelopeContent content) {
|
||||
int size = content.size();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.messages.EnvelopeResponse;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
|
||||
|
@ -11,7 +11,6 @@ import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
|
|||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketResponseMessage;
|
||||
import org.whispersystems.signalservice.internal.websocket.WebsocketResponse;
|
||||
import org.signal.core.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -19,6 +18,8 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
|
@ -198,10 +199,11 @@ public final class SignalWebSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public Single<WebsocketResponse> request(WebSocketRequestMessage requestMessage, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
public Single<WebsocketResponse> request(WebSocketRequestMessage requestMessage, @Nullable SealedSenderAccess sealedSenderAccess) {
|
||||
if (sealedSenderAccess != null) {
|
||||
List<String> headers = new ArrayList<>(requestMessage.headers);
|
||||
headers.add("Unidentified-Access-Key:" + Base64.encodeWithPadding(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
headers.add(sealedSenderAccess.getHeader());
|
||||
|
||||
WebSocketRequestMessage message = requestMessage.newBuilder()
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
@ -209,7 +211,7 @@ public final class SignalWebSocket {
|
|||
return getUnidentifiedWebSocket().sendRequest(message)
|
||||
.flatMap(r -> {
|
||||
if (r.getStatus() == 401) {
|
||||
return request(requestMessage);
|
||||
return request(requestMessage, sealedSenderAccess.switchToFallback());
|
||||
}
|
||||
return Single.just(r);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.crypto
|
||||
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements
|
||||
import org.whispersystems.util.ByteArrayUtil
|
||||
|
||||
/**
|
||||
* Provides single interface for the various ways to send via sealed sender.
|
||||
*/
|
||||
sealed class SealedSenderAccess {
|
||||
|
||||
abstract val senderCertificate: SenderCertificate
|
||||
abstract val headerName: String
|
||||
abstract val headerValue: String
|
||||
|
||||
val header: String
|
||||
get() = "$headerName:$headerValue"
|
||||
|
||||
abstract fun switchToFallback(): SealedSenderAccess?
|
||||
|
||||
/**
|
||||
* For sending to an single recipient using group send endorsement/token first and then fallback to
|
||||
* access key if available.
|
||||
*/
|
||||
class IndividualGroupSendTokenFirst(
|
||||
private val groupSendToken: GroupSendFullToken,
|
||||
override val senderCertificate: SenderCertificate,
|
||||
val unidentifiedAccess: UnidentifiedAccess? = null
|
||||
) : SealedSenderAccess() {
|
||||
|
||||
override val headerName: String = "Group-Send-Token"
|
||||
override val headerValue: String by lazy { Base64.encodeWithPadding(groupSendToken.serialize()) }
|
||||
|
||||
override fun switchToFallback(): SealedSenderAccess? {
|
||||
fallbackListener?.onTokenToAccessFallback(unidentifiedAccess != null)
|
||||
return if (unidentifiedAccess != null) {
|
||||
IndividualUnidentifiedAccessFirst(unidentifiedAccess)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sending to an single recipient using access key first and then fallback to group send
|
||||
* token if available. The token is created lazily via the provided [createGroupSendToken] function.
|
||||
*/
|
||||
class IndividualUnidentifiedAccessFirst(
|
||||
val unidentifiedAccess: UnidentifiedAccess,
|
||||
private val createGroupSendToken: CreateGroupSendToken? = null
|
||||
) : SealedSenderAccess() {
|
||||
|
||||
override val senderCertificate: SenderCertificate
|
||||
get() = unidentifiedAccess.unidentifiedCertificate
|
||||
|
||||
override val headerName: String = "Unidentified-Access-Key"
|
||||
override val headerValue: String by lazy { Base64.encodeWithPadding(unidentifiedAccess.unidentifiedAccessKey) }
|
||||
|
||||
override fun switchToFallback(): SealedSenderAccess? {
|
||||
val groupSendToken = createGroupSendToken?.create()
|
||||
return if (groupSendToken != null) {
|
||||
fallbackListener?.onAccessToTokenFallback()
|
||||
IndividualGroupSendTokenFirst(groupSendToken, senderCertificate)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sending to a "group" of recipients using group send endorsements/tokens.
|
||||
*/
|
||||
class GroupGroupSendToken(
|
||||
private val groupSendEndorsements: GroupSendEndorsements
|
||||
) : SealedSenderAccess() {
|
||||
|
||||
override val headerName: String = "Group-Send-Token"
|
||||
override val headerValue: String by lazy { Base64.encodeWithPadding(groupSendEndorsements.serialize()) }
|
||||
|
||||
override val senderCertificate: SenderCertificate
|
||||
get() = groupSendEndorsements.sealedSenderCertificate
|
||||
|
||||
override fun switchToFallback(): SealedSenderAccess? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sending to a "group" of recipients using access keys.
|
||||
*/
|
||||
class GroupUnidentifiedAccess(
|
||||
private val unidentifiedAccess: List<UnidentifiedAccess>,
|
||||
override val senderCertificate: SenderCertificate = unidentifiedAccess.first().unidentifiedCertificate
|
||||
) : SealedSenderAccess() {
|
||||
|
||||
override val headerName: String = "Unidentified-Access-Key"
|
||||
override val headerValue: String by lazy {
|
||||
var joinedUnidentifiedAccess = ByteArray(16)
|
||||
for (access in unidentifiedAccess) {
|
||||
joinedUnidentifiedAccess = ByteArrayUtil.xor(joinedUnidentifiedAccess, access.unidentifiedAccessKey)
|
||||
}
|
||||
|
||||
Base64.encodeWithPadding(joinedUnidentifiedAccess)
|
||||
}
|
||||
|
||||
override fun switchToFallback(): SealedSenderAccess? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a lazy way to create a group send token.
|
||||
*/
|
||||
fun interface CreateGroupSendToken {
|
||||
fun create(): GroupSendFullToken?
|
||||
}
|
||||
|
||||
interface FallbackListener {
|
||||
fun onAccessToTokenFallback()
|
||||
fun onTokenToAccessFallback(hasAccessKeyFallback: Boolean)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var fallbackListener: FallbackListener? = null
|
||||
|
||||
@JvmField
|
||||
val NONE: SealedSenderAccess? = null
|
||||
|
||||
@JvmStatic
|
||||
fun forIndividualWithGroupFallback(
|
||||
unidentifiedAccess: UnidentifiedAccess?,
|
||||
senderCertificate: SenderCertificate?,
|
||||
createGroupSendToken: CreateGroupSendToken?
|
||||
): SealedSenderAccess? {
|
||||
if (unidentifiedAccess != null) {
|
||||
return IndividualUnidentifiedAccessFirst(unidentifiedAccess, createGroupSendToken)
|
||||
}
|
||||
|
||||
val groupSendToken = createGroupSendToken?.create()
|
||||
if (groupSendToken != null && senderCertificate != null) {
|
||||
return IndividualGroupSendTokenFirst(groupSendToken, senderCertificate)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forIndividual(unidentifiedAccess: UnidentifiedAccess?): SealedSenderAccess? {
|
||||
return unidentifiedAccess?.let { IndividualUnidentifiedAccessFirst(it) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forFanOutGroupSend(groupSendTokens: List<GroupSendFullToken?>?, senderCertificate: SenderCertificate?, unidentifiedAccesses: List<UnidentifiedAccess?>): List<SealedSenderAccess?> {
|
||||
if (groupSendTokens == null) {
|
||||
return unidentifiedAccesses.map { a -> forIndividual(a) }
|
||||
}
|
||||
|
||||
require(groupSendTokens.size == unidentifiedAccesses.size)
|
||||
|
||||
return groupSendTokens
|
||||
.zip(unidentifiedAccesses)
|
||||
.map { (token, unidentifiedAccess) ->
|
||||
if (unidentifiedAccess != null) {
|
||||
IndividualUnidentifiedAccessFirst(unidentifiedAccess) { token }
|
||||
} else if (token != null && senderCertificate != null) {
|
||||
IndividualGroupSendTokenFirst(token, senderCertificate)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forGroupSend(groupSendEndorsements: GroupSendEndorsements?, unidentifiedAccess: List<UnidentifiedAccess>, forStory: Boolean): SealedSenderAccess {
|
||||
return if (groupSendEndorsements != null && !forStory) {
|
||||
GroupGroupSendToken(groupSendEndorsements)
|
||||
} else {
|
||||
GroupUnidentifiedAccess(unidentifiedAccess)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isUnrestrictedForStory(sealedSenderAccess: SealedSenderAccess?): Boolean {
|
||||
return when (sealedSenderAccess) {
|
||||
is IndividualGroupSendTokenFirst -> sealedSenderAccess.unidentifiedAccess?.isUnrestrictedForStory ?: false
|
||||
is IndividualUnidentifiedAccessFirst -> sealedSenderAccess.unidentifiedAccess.isUnrestrictedForStory
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -60,6 +60,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This is used to encrypt + decrypt received envelopes.
|
||||
*/
|
||||
|
@ -110,17 +112,17 @@ public class SignalServiceCipher {
|
|||
}
|
||||
|
||||
public OutgoingPushMessage encrypt(SignalProtocolAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
EnvelopeContent content)
|
||||
throws UntrustedIdentityException, InvalidKeyException
|
||||
{
|
||||
try {
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, destination));
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
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, unidentifiedAccess.get().getUnidentifiedCertificate());
|
||||
return content.processSealedSender(sessionCipher, sealedSessionCipher, destination, sealedSenderAccess.getSenderCertificate());
|
||||
} else {
|
||||
return content.processUnsealedSender(sessionCipher, destination);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,6 @@ public class UnidentifiedAccess {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static byte[] createEmptyByteArray(int length) {
|
||||
return new byte[length];
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class UnidentifiedAccessPair {
|
||||
|
||||
private final Optional<UnidentifiedAccess> targetUnidentifiedAccess;
|
||||
private final Optional<UnidentifiedAccess> selfUnidentifiedAccess;
|
||||
|
||||
public UnidentifiedAccessPair(UnidentifiedAccess targetUnidentifiedAccess, UnidentifiedAccess selfUnidentifiedAccess) {
|
||||
this.targetUnidentifiedAccess = Optional.of(targetUnidentifiedAccess);
|
||||
this.selfUnidentifiedAccess = Optional.of(selfUnidentifiedAccess);
|
||||
}
|
||||
|
||||
public Optional<UnidentifiedAccess> getTargetUnidentifiedAccess() {
|
||||
return targetUnidentifiedAccess;
|
||||
}
|
||||
|
||||
public Optional<UnidentifiedAccess> getSelfUnidentifiedAccess() {
|
||||
return selfUnidentifiedAccess;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.groupsv2
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
|
||||
/**
|
||||
* Decrypted response from server operations that includes our global group state and
|
||||
* our specific-to-us group send endorsements.
|
||||
*/
|
||||
class DecryptedGroupResponse(
|
||||
val group: DecryptedGroup,
|
||||
val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?
|
||||
)
|
|
@ -1,11 +1,12 @@
|
|||
package org.whispersystems.signalservice.api.groupsv2
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket.GroupHistory
|
||||
|
||||
/**
|
||||
* Wraps result of group history fetch with it's associated paging data.
|
||||
*/
|
||||
data class GroupHistoryPage(val changeLogs: List<DecryptedGroupChangeLog>, val pagingData: PagingData) {
|
||||
data class GroupHistoryPage(val changeLogs: List<DecryptedGroupChangeLog>, val groupSendEndorsementsResponse: GroupSendEndorsementsResponse?, val pagingData: PagingData) {
|
||||
|
||||
data class PagingData(val hasMorePages: Boolean, val nextPageRevision: Int) {
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.groupsv2
|
||||
|
||||
import org.signal.libsignal.metadata.certificate.SenderCertificate
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Helper container for all data needed to send with group send endorsements.
|
||||
*/
|
||||
data class GroupSendEndorsements(
|
||||
val expirationMs: Long,
|
||||
val endorsements: Map<ServiceId.ACI, GroupSendEndorsement>,
|
||||
val sealedSenderCertificate: SenderCertificate,
|
||||
val groupSecretParams: GroupSecretParams
|
||||
) {
|
||||
|
||||
private val expiration: Instant by lazy { Instant.ofEpochMilli(expirationMs) }
|
||||
private val combinedEndorsement: GroupSendEndorsement by lazy { GroupSendEndorsement.combine(endorsements.values) }
|
||||
|
||||
fun serialize(): ByteArray {
|
||||
return combinedEndorsement.toFullToken(groupSecretParams, expiration).serialize()
|
||||
}
|
||||
|
||||
fun forIndividuals(addresses: List<SignalServiceAddress>): List<GroupSendFullToken?> {
|
||||
return addresses
|
||||
.map { a -> endorsements[a.serviceId] }
|
||||
.map { e -> e?.toFullToken(groupSecretParams, expiration) }
|
||||
}
|
||||
}
|
|
@ -9,13 +9,16 @@ import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations;
|
|||
import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse;
|
||||
import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse;
|
||||
import org.signal.storageservice.protos.groups.AvatarUploadAttributes;
|
||||
import org.signal.storageservice.protos.groups.Group;
|
||||
import org.signal.storageservice.protos.groups.GroupAttributeBlob;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse;
|
||||
import org.signal.storageservice.protos.groups.GroupChanges;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||
import org.signal.storageservice.protos.groups.GroupResponse;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
|
||||
|
@ -31,6 +34,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@ -73,9 +77,9 @@ public class GroupsV2Api {
|
|||
return new GroupsV2AuthorizationString(groupSecretParams, authCredentialPresentation);
|
||||
}
|
||||
|
||||
public void putNewGroup(GroupsV2Operations.NewGroup newGroup,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException
|
||||
public DecryptedGroupResponse putNewGroup(GroupsV2Operations.NewGroup newGroup,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException, InvalidGroupStateException, VerificationFailedException, InvalidInputException
|
||||
{
|
||||
Group group = newGroup.getNewGroupMessage();
|
||||
|
||||
|
@ -83,34 +87,38 @@ public class GroupsV2Api {
|
|||
String cdnKey = uploadAvatar(newGroup.getAvatar().get(), newGroup.getGroupSecretParams(), authorization);
|
||||
|
||||
group = group.newBuilder()
|
||||
.avatar(cdnKey)
|
||||
.build();
|
||||
.avatar(cdnKey)
|
||||
.build();
|
||||
}
|
||||
|
||||
socket.putNewGroupsV2Group(group, authorization);
|
||||
GroupResponse response = socket.putNewGroupsV2Group(group, authorization);
|
||||
|
||||
return groupsOperations.forGroup(newGroup.getGroupSecretParams())
|
||||
.decryptGroup(Objects.requireNonNull(response.group), response.groupSendEndorsementsResponse.toByteArray());
|
||||
}
|
||||
|
||||
public NetworkResult<DecryptedGroup> getGroupAsResult(GroupSecretParams groupSecretParams, GroupsV2AuthorizationString authorization) {
|
||||
public NetworkResult<DecryptedGroupResponse> getGroupAsResult(GroupSecretParams groupSecretParams, GroupsV2AuthorizationString authorization) {
|
||||
return NetworkResult.fromFetch(() -> getGroup(groupSecretParams, authorization));
|
||||
}
|
||||
|
||||
public DecryptedGroup getGroup(GroupSecretParams groupSecretParams,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException, InvalidGroupStateException, VerificationFailedException
|
||||
public DecryptedGroupResponse getGroup(GroupSecretParams groupSecretParams,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException, InvalidGroupStateException, VerificationFailedException, InvalidInputException
|
||||
{
|
||||
Group group = socket.getGroupsV2Group(authorization);
|
||||
GroupResponse response = socket.getGroupsV2Group(authorization);
|
||||
|
||||
return groupsOperations.forGroup(groupSecretParams)
|
||||
.decryptGroup(group);
|
||||
.decryptGroup(Objects.requireNonNull(response.group), response.groupSendEndorsementsResponse.toByteArray());
|
||||
}
|
||||
|
||||
public GroupHistoryPage getGroupHistoryPage(GroupSecretParams groupSecretParams,
|
||||
int fromRevision,
|
||||
GroupsV2AuthorizationString authorization,
|
||||
boolean includeFirstState)
|
||||
throws IOException, InvalidGroupStateException, VerificationFailedException
|
||||
boolean includeFirstState,
|
||||
long sendEndorsementsExpirationMs)
|
||||
throws IOException, InvalidGroupStateException, VerificationFailedException, InvalidInputException
|
||||
{
|
||||
PushServiceSocket.GroupHistory group = socket.getGroupsV2GroupHistory(fromRevision, authorization, GroupsV2Operations.HIGHEST_KNOWN_EPOCH, includeFirstState);
|
||||
PushServiceSocket.GroupHistory group = socket.getGroupHistory(fromRevision, authorization, GroupsV2Operations.HIGHEST_KNOWN_EPOCH, includeFirstState, sendEndorsementsExpirationMs);
|
||||
List<DecryptedGroupChangeLog> result = new ArrayList<>(group.getGroupChanges().groupChanges.size());
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsOperations.forGroup(groupSecretParams);
|
||||
|
||||
|
@ -121,7 +129,10 @@ public class GroupsV2Api {
|
|||
result.add(new DecryptedGroupChangeLog(decryptedGroup, decryptedChange));
|
||||
}
|
||||
|
||||
return new GroupHistoryPage(result, GroupHistoryPage.PagingData.forGroupHistory(group));
|
||||
byte[] groupSendEndorsementsResponseBytes = group.getGroupChanges().groupSendEndorsementsResponse.toByteArray();
|
||||
GroupSendEndorsementsResponse groupSendEndorsementsResponse = groupSendEndorsementsResponseBytes.length > 0 ? new GroupSendEndorsementsResponse(groupSendEndorsementsResponseBytes) : null;
|
||||
|
||||
return new GroupHistoryPage(result, groupSendEndorsementsResponse, GroupHistoryPage.PagingData.forGroupHistory(group));
|
||||
}
|
||||
|
||||
public NetworkResult<Integer> getGroupJoinedAt(@Nonnull GroupsV2AuthorizationString authorization) {
|
||||
|
@ -162,9 +173,9 @@ public class GroupsV2Api {
|
|||
return form.key;
|
||||
}
|
||||
|
||||
public GroupChange patchGroup(GroupChange.Actions groupChange,
|
||||
GroupsV2AuthorizationString authorization,
|
||||
Optional<byte[]> groupLinkPassword)
|
||||
public GroupChangeResponse patchGroup(GroupChange.Actions groupChange,
|
||||
GroupsV2AuthorizationString authorization,
|
||||
Optional<byte[]> groupLinkPassword)
|
||||
throws IOException
|
||||
{
|
||||
return socket.patchGroupsV2Group(groupChange, authorization.toString(), groupLinkPassword);
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
|
|||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
|
||||
import org.signal.libsignal.zkgroup.groups.ProfileKeyCiphertext;
|
||||
import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
|
@ -53,6 +54,9 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
|
@ -154,7 +158,7 @@ public final class GroupsV2Operations {
|
|||
private final GroupSecretParams groupSecretParams;
|
||||
private final ClientZkGroupCipher clientZkGroupCipher;
|
||||
|
||||
private GroupOperations(GroupSecretParams groupSecretParams) {
|
||||
public GroupOperations(GroupSecretParams groupSecretParams) {
|
||||
this.groupSecretParams = groupSecretParams;
|
||||
this.clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
|
||||
}
|
||||
|
@ -425,6 +429,15 @@ public final class GroupsV2Operations {
|
|||
return new PendingMember.Builder().member(member);
|
||||
}
|
||||
|
||||
public @Nonnull DecryptedGroupResponse decryptGroup(@Nonnull Group group, @Nonnull byte[] groupSendEndorsementsBytes)
|
||||
throws VerificationFailedException, InvalidGroupStateException, InvalidInputException
|
||||
{
|
||||
DecryptedGroup decryptedGroup = decryptGroup(group);
|
||||
GroupSendEndorsementsResponse groupSendEndorsementsResponse = groupSendEndorsementsBytes.length > 0 ? new GroupSendEndorsementsResponse(groupSendEndorsementsBytes) : null;
|
||||
|
||||
return new DecryptedGroupResponse(decryptedGroup, groupSendEndorsementsResponse);
|
||||
}
|
||||
|
||||
public DecryptedGroup decryptGroup(Group group)
|
||||
throws VerificationFailedException, InvalidGroupStateException
|
||||
{
|
||||
|
@ -1019,6 +1032,46 @@ public final class GroupsV2Operations {
|
|||
return ids;
|
||||
}
|
||||
|
||||
public @Nullable ReceivedGroupSendEndorsements receiveGroupSendEndorsements(@Nonnull ACI selfAci,
|
||||
@Nonnull DecryptedGroup decryptedGroup,
|
||||
@Nullable ByteString groupSendEndorsementsResponse)
|
||||
{
|
||||
if (groupSendEndorsementsResponse != null && groupSendEndorsementsResponse.size() > 0) {
|
||||
try {
|
||||
return receiveGroupSendEndorsements(selfAci, decryptedGroup, new GroupSendEndorsementsResponse(groupSendEndorsementsResponse.toByteArray()));
|
||||
} catch (InvalidInputException e) {
|
||||
Log.w(TAG, "Unable to parse send endorsements response", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable ReceivedGroupSendEndorsements receiveGroupSendEndorsements(@Nonnull ACI selfAci,
|
||||
@Nonnull DecryptedGroup decryptedGroup,
|
||||
@Nullable GroupSendEndorsementsResponse groupSendEndorsementsResponse)
|
||||
{
|
||||
if (groupSendEndorsementsResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ACI> members = decryptedGroup.members.stream().map(m -> ACI.parseOrThrow(m.aciBytes)).collect(Collectors.toList());
|
||||
|
||||
GroupSendEndorsementsResponse.ReceivedEndorsements endorsements = null;
|
||||
try {
|
||||
endorsements = groupSendEndorsementsResponse.receive(
|
||||
members.stream().map(ACI::getLibSignalAci).collect(Collectors.toList()),
|
||||
selfAci.getLibSignalAci(),
|
||||
groupSecretParams,
|
||||
serverPublicParams
|
||||
);
|
||||
} catch (VerificationFailedException e) {
|
||||
Log.w(TAG, "Unable to receive send endorsements for group", e);
|
||||
}
|
||||
|
||||
return endorsements != null ? new ReceivedGroupSendEndorsements(groupSendEndorsementsResponse.getExpiration(), members, endorsements)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NewGroup {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.groupsv2
|
||||
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Group send endorsement data received from the server.
|
||||
*/
|
||||
data class ReceivedGroupSendEndorsements(
|
||||
val expirationMs: Long,
|
||||
val endorsements: Map<ServiceId.ACI, GroupSendEndorsement>
|
||||
) {
|
||||
constructor(
|
||||
expiration: Instant,
|
||||
members: List<ServiceId.ACI>,
|
||||
receivedEndorsements: GroupSendEndorsementsResponse.ReceivedEndorsements
|
||||
) : this(
|
||||
expirationMs = expiration.toEpochMilli(),
|
||||
endorsements = members.zip(receivedEndorsements.endorsements).toMap()
|
||||
)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.whispersystems.signalservice.api.services;
|
||||
|
||||
import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
@ -19,13 +19,14 @@ import org.whispersystems.signalservice.internal.util.Util;
|
|||
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
|
||||
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
|
||||
import org.signal.core.util.Base64;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import okio.ByteString;
|
||||
|
@ -42,7 +43,9 @@ public class MessagingService {
|
|||
this.signalWebSocket = signalWebSocket;
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<SendMessageResponse>> send(OutgoingPushMessageList list, Optional<UnidentifiedAccess> unidentifiedAccess, boolean story) {
|
||||
public Single<ServiceResponse<SendMessageResponse>> send(OutgoingPushMessageList list,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
boolean story) {
|
||||
List<String> headers = new LinkedList<String>() {{
|
||||
add("content-type:application/json");
|
||||
}};
|
||||
|
@ -66,15 +69,15 @@ public class MessagingService {
|
|||
.withCustomError(404, (status, body, getHeader) -> new UnregisteredUserException(list.getDestination(), new NotFoundException("not found")))
|
||||
.build();
|
||||
|
||||
return signalWebSocket.request(requestMessage, unidentifiedAccess)
|
||||
return signalWebSocket.request(requestMessage, sealedSenderAccess)
|
||||
.map(responseMapper::map)
|
||||
.onErrorReturn(ServiceResponse::forUnknownError);
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<SendGroupMessageResponse>> sendToGroup(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent, boolean story) {
|
||||
public Single<ServiceResponse<SendGroupMessageResponse>> sendToGroup(byte[] body, @Nonnull SealedSenderAccess sealedSenderAccess, long timestamp, boolean online, boolean urgent, boolean story) {
|
||||
List<String> headers = new LinkedList<String>() {{
|
||||
add("content-type:application/vnd.signal-messenger.mrm");
|
||||
add("Unidentified-Access-Key:" + Base64.encodeWithPadding(joinedUnidentifiedAccess));
|
||||
add(sealedSenderAccess.getHeader());
|
||||
}};
|
||||
|
||||
String path = String.format(Locale.US, "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s&story=%s", timestamp, online, urgent, story);
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
|
|||
import org.signal.libsignal.zkgroup.profiles.ProfileKeyVersion;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
import org.whispersystems.signalservice.api.SignalWebSocket;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
@ -44,6 +45,7 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
|
@ -72,7 +74,7 @@ public final class ProfileService {
|
|||
|
||||
public Single<ServiceResponse<ProfileAndCredential>> getProfile(@Nonnull SignalServiceAddress address,
|
||||
@Nonnull Optional<ProfileKey> profileKey,
|
||||
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
@Nonnull SignalServiceProfile.RequestType requestType,
|
||||
@Nonnull Locale locale)
|
||||
{
|
||||
|
@ -116,9 +118,9 @@ public final class ProfileService {
|
|||
.withResponseMapper(new ProfileResponseMapper(requestType, requestContext))
|
||||
.build();
|
||||
|
||||
return signalWebSocket.request(requestMessage, unidentifiedAccess)
|
||||
return signalWebSocket.request(requestMessage, sealedSenderAccess)
|
||||
.map(responseMapper::map)
|
||||
.onErrorResumeNext(t -> getProfileRestFallback(address, profileKey, unidentifiedAccess, requestType, locale))
|
||||
.onErrorResumeNext(t -> getProfileRestFallback(address, profileKey, sealedSenderAccess, requestType, locale))
|
||||
.onErrorReturn(ServiceResponse::forUnknownError);
|
||||
}
|
||||
|
||||
|
@ -139,19 +141,19 @@ public final class ProfileService {
|
|||
|
||||
ResponseMapper<IdentityCheckResponse> responseMapper = DefaultResponseMapper.getDefault(IdentityCheckResponse.class);
|
||||
|
||||
return signalWebSocket.request(builder.build(), Optional.empty())
|
||||
return signalWebSocket.request(builder.build(), SealedSenderAccess.NONE)
|
||||
.map(responseMapper::map)
|
||||
.onErrorResumeNext(t -> performIdentityCheckRestFallback(request, Optional.empty(), responseMapper))
|
||||
.onErrorResumeNext(t -> performIdentityCheckRestFallback(request, responseMapper))
|
||||
.onErrorReturn(ServiceResponse::forUnknownError);
|
||||
}
|
||||
|
||||
private Single<ServiceResponse<ProfileAndCredential>> getProfileRestFallback(@Nonnull SignalServiceAddress address,
|
||||
@Nonnull Optional<ProfileKey> profileKey,
|
||||
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
@Nonnull SignalServiceProfile.RequestType requestType,
|
||||
@Nonnull Locale locale)
|
||||
{
|
||||
return Single.fromFuture(receiver.retrieveProfile(address, profileKey, unidentifiedAccess, requestType, locale), 10, TimeUnit.SECONDS)
|
||||
return Single.fromFuture(receiver.retrieveProfile(address, profileKey, sealedSenderAccess, requestType, locale), 10, TimeUnit.SECONDS)
|
||||
.onErrorResumeNext(t -> {
|
||||
Throwable error;
|
||||
if (t instanceof ExecutionException && t.getCause() != null) {
|
||||
|
@ -161,7 +163,7 @@ public final class ProfileService {
|
|||
}
|
||||
|
||||
if (error instanceof AuthorizationFailedException) {
|
||||
return Single.fromFuture(receiver.retrieveProfile(address, profileKey, Optional.empty(), requestType, locale), 10, TimeUnit.SECONDS);
|
||||
return Single.fromFuture(receiver.retrieveProfile(address, profileKey, null, requestType, locale), 10, TimeUnit.SECONDS);
|
||||
} else {
|
||||
return Single.error(t);
|
||||
}
|
||||
|
@ -170,9 +172,8 @@ public final class ProfileService {
|
|||
}
|
||||
|
||||
private @NonNull Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheckRestFallback(@Nonnull IdentityCheckRequest request,
|
||||
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nonnull ResponseMapper<IdentityCheckResponse> responseMapper) {
|
||||
return receiver.performIdentityCheck(request, unidentifiedAccess, responseMapper)
|
||||
return receiver.performIdentityCheck(request, responseMapper)
|
||||
.onErrorResumeNext(t -> {
|
||||
Throwable error;
|
||||
if (t instanceof ExecutionException && t.getCause() != null) {
|
||||
|
@ -182,7 +183,7 @@ public final class ProfileService {
|
|||
}
|
||||
|
||||
if (error instanceof AuthorizationFailedException) {
|
||||
return receiver.performIdentityCheck(request, Optional.empty(), responseMapper);
|
||||
return receiver.performIdentityCheck(request, responseMapper);
|
||||
} else {
|
||||
return Single.error(t);
|
||||
}
|
||||
|
|
|
@ -39,9 +39,11 @@ import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse;
|
|||
import org.signal.storageservice.protos.groups.AvatarUploadAttributes;
|
||||
import org.signal.storageservice.protos.groups.Group;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.GroupChangeResponse;
|
||||
import org.signal.storageservice.protos.groups.GroupChanges;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.storageservice.protos.groups.GroupJoinInfo;
|
||||
import org.signal.storageservice.protos.groups.GroupResponse;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
|
||||
|
@ -60,7 +62,7 @@ import org.whispersystems.signalservice.api.archive.BatchArchiveMediaRequest;
|
|||
import org.whispersystems.signalservice.api.archive.BatchArchiveMediaResponse;
|
||||
import org.whispersystems.signalservice.api.archive.DeleteArchivedMediaRequest;
|
||||
import org.whispersystems.signalservice.api.archive.GetArchiveCdnCredentialsResponse;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||
|
@ -273,13 +275,13 @@ public class PushServiceSocket {
|
|||
private static final String STICKER_PATH = "stickers/%s/full/%d";
|
||||
|
||||
private static final String GROUPSV2_CREDENTIAL = "/v1/certificate/auth/group?redemptionStartSeconds=%d&redemptionEndSeconds=%d&zkcCredential=true";
|
||||
private static final String GROUPSV2_GROUP = "/v1/groups/";
|
||||
private static final String GROUPSV2_GROUP_PASSWORD = "/v1/groups/?inviteLinkPassword=%s";
|
||||
private static final String GROUPSV2_GROUP_CHANGES = "/v1/groups/logs/%s?maxSupportedChangeEpoch=%d&includeFirstState=%s&includeLastState=false";
|
||||
private static final String GROUPSV2_AVATAR_REQUEST = "/v1/groups/avatar/form";
|
||||
private static final String GROUPSV2_GROUP_JOIN = "/v1/groups/join/%s";
|
||||
private static final String GROUPSV2_TOKEN = "/v1/groups/token";
|
||||
private static final String GROUPSV2_JOINED_AT = "/v1/groups/joined_at_version";
|
||||
private static final String GROUPSV2_GROUP = "/v2/groups/";
|
||||
private static final String GROUPSV2_GROUP_PASSWORD = "/v2/groups/?inviteLinkPassword=%s";
|
||||
private static final String GROUPSV2_GROUP_CHANGES = "/v2/groups/logs/%s?maxSupportedChangeEpoch=%d&includeFirstState=%s&includeLastState=false";
|
||||
private static final String GROUPSV2_AVATAR_REQUEST = "/v2/groups/avatar/form";
|
||||
private static final String GROUPSV2_GROUP_JOIN = "/v2/groups/join/%s";
|
||||
private static final String GROUPSV2_TOKEN = "/v2/groups/token";
|
||||
private static final String GROUPSV2_JOINED_AT = "/v2/groups/joined_at_version";
|
||||
|
||||
private static final String PAYMENTS_CONVERSIONS = "/v1/payments/conversions";
|
||||
|
||||
|
@ -376,7 +378,7 @@ public class PushServiceSocket {
|
|||
|
||||
public RegistrationSessionMetadataResponse createVerificationSession(@Nullable String pushToken, @Nullable String mcc, @Nullable String mnc) throws IOException {
|
||||
final String jsonBody = JsonUtil.toJson(new VerificationSessionMetadataRequestBody(credentialsProvider.getE164(), pushToken, mcc, mnc));
|
||||
try (Response response = makeServiceRequest(VERIFICATION_SESSION_PATH, "POST", jsonRequestBody(jsonBody), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty(), false)) {
|
||||
try (Response response = makeServiceRequest(VERIFICATION_SESSION_PATH, "POST", jsonRequestBody(jsonBody), NO_HEADERS, new RegistrationSessionResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||
return parseSessionMetadataResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +386,7 @@ public class PushServiceSocket {
|
|||
public RegistrationSessionMetadataResponse getSessionStatus(String sessionId) throws IOException {
|
||||
String path = VERIFICATION_SESSION_PATH + "/" + sessionId;
|
||||
|
||||
try (Response response = makeServiceRequest(path, "GET", jsonRequestBody(null), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty(), false)) {
|
||||
try (Response response = makeServiceRequest(path, "GET", jsonRequestBody(null), NO_HEADERS, new RegistrationSessionResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||
return parseSessionMetadataResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +395,7 @@ public class PushServiceSocket {
|
|||
String path = VERIFICATION_SESSION_PATH + "/" + sessionId;
|
||||
|
||||
final UpdateVerificationSessionRequestBody requestBody = new UpdateVerificationSessionRequestBody(captchaToken, pushToken, pushChallengeToken, mcc, mnc);
|
||||
try (Response response = makeServiceRequest(path, "PATCH", jsonRequestBody(JsonUtil.toJson(requestBody)), NO_HEADERS, new PatchRegistrationSessionResponseHandler(), Optional.empty(), false)) {
|
||||
try (Response response = makeServiceRequest(path, "PATCH", jsonRequestBody(JsonUtil.toJson(requestBody)), NO_HEADERS, new PatchRegistrationSessionResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||
return parseSessionMetadataResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -414,7 +416,7 @@ public class PushServiceSocket {
|
|||
|
||||
body.put("client", androidSmsRetriever ? "android-2021-03" : "android");
|
||||
|
||||
try (Response response = makeServiceRequest(path, "POST", jsonRequestBody(JsonUtil.toJson(body)), headers, new RegistrationCodeRequestResponseHandler(), Optional.empty(), false)) {
|
||||
try (Response response = makeServiceRequest(path, "POST", jsonRequestBody(JsonUtil.toJson(body)), headers, new RegistrationCodeRequestResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||
return parseSessionMetadataResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -423,7 +425,7 @@ public class PushServiceSocket {
|
|||
String path = String.format(VERIFICATION_CODE_PATH, sessionId);
|
||||
Map<String, String> body = new HashMap<>();
|
||||
body.put("code", verificationCode);
|
||||
try (Response response = makeServiceRequest(path, "PUT", jsonRequestBody(JsonUtil.toJson(body)), NO_HEADERS, new RegistrationCodeSubmissionResponseHandler(), Optional.empty(), false)) {
|
||||
try (Response response = makeServiceRequest(path, "PUT", jsonRequestBody(JsonUtil.toJson(body)), NO_HEADERS, new RegistrationCodeSubmissionResponseHandler(), SealedSenderAccess.NONE, false)) {
|
||||
return parseSessionMetadataResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -470,7 +472,7 @@ public class PushServiceSocket {
|
|||
skipDeviceTransfer,
|
||||
true);
|
||||
|
||||
String response = makeServiceRequest(path, "POST", JsonUtil.toJson(body), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty());
|
||||
String response = makeServiceRequest(path, "POST", JsonUtil.toJson(body), NO_HEADERS, new RegistrationSessionResponseHandler(), SealedSenderAccess.NONE);
|
||||
return JsonUtil.fromJson(response, VerifyAccountResponse.class);
|
||||
}
|
||||
|
||||
|
@ -512,14 +514,14 @@ public class PushServiceSocket {
|
|||
long secondsRoundedToNearestDay = TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(currentTime));
|
||||
long endTimeInSeconds = secondsRoundedToNearestDay + TimeUnit.DAYS.toSeconds(7);
|
||||
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null, NO_HEADERS, UNOPINIONATED_HANDLER, Optional.empty());
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null, NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveServiceCredentialsResponse.class);
|
||||
}
|
||||
|
||||
public void setArchiveBackupId(BackupAuthCredentialRequest request) throws IOException {
|
||||
String body = JsonUtil.toJson(new ArchiveSetBackupIdRequest(request));
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body, NO_HEADERS, UNOPINIONATED_HANDLER, Optional.empty());
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body, NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
}
|
||||
|
||||
public void setArchivePublicKey(ECPublicKey publicKey, ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
|
@ -706,7 +708,7 @@ public class PushServiceSocket {
|
|||
return JsonUtil.fromJson(responseText, SenderCertificate.class).getCertificate();
|
||||
}
|
||||
|
||||
public SendGroupMessageResponse sendGroupMessage(byte[] body, byte[] joinedUnidentifiedAccess, long timestamp, boolean online, boolean urgent, boolean story)
|
||||
public SendGroupMessageResponse sendGroupMessage(byte[] body, @Nonnull SealedSenderAccess sealedSenderAccess, long timestamp, boolean online, boolean urgent, boolean story)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, MalformedResponseException
|
||||
{
|
||||
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
|
||||
|
@ -716,7 +718,7 @@ public class PushServiceSocket {
|
|||
Request.Builder requestBuilder = new Request.Builder();
|
||||
requestBuilder.url(String.format("%s%s", connectionHolder.getUrl(), path));
|
||||
requestBuilder.put(RequestBody.create(MediaType.get("application/vnd.signal-messenger.mrm"), body));
|
||||
requestBuilder.addHeader("Unidentified-Access-Key", Base64.encodeWithPadding(joinedUnidentifiedAccess));
|
||||
requestBuilder.addHeader(sealedSenderAccess.getHeaderName(), sealedSenderAccess.getHeaderValue());
|
||||
|
||||
if (signalAgent != null) {
|
||||
requestBuilder.addHeader("X-Signal-Agent", signalAgent);
|
||||
|
@ -762,14 +764,14 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, Optional<UnidentifiedAccess> unidentifiedAccess, boolean story)
|
||||
public SendMessageResponse sendMessage(OutgoingPushMessageList bundle, @Nullable SealedSenderAccess sealedSenderAccess, boolean story)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
String responseText = makeServiceRequest(String.format("/v1/messages/%s?story=%s", bundle.getDestination(), story ? "true" : "false"), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, NO_HANDLER, unidentifiedAccess);
|
||||
String responseText = makeServiceRequest(String.format("/v1/messages/%s?story=%s", bundle.getDestination(), story ? "true" : "false"), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, NO_HANDLER, sealedSenderAccess);
|
||||
SendMessageResponse response = JsonUtil.fromJson(responseText, SendMessageResponse.class);
|
||||
|
||||
response.setSentUnidentfied(unidentifiedAccess.isPresent());
|
||||
response.setSentUnidentfied(sealedSenderAccess != null);
|
||||
|
||||
return response;
|
||||
} catch (NotFoundException nfe) {
|
||||
|
@ -780,7 +782,7 @@ public class PushServiceSocket {
|
|||
public SignalServiceMessagesResult getMessages(boolean allowStories) throws IOException {
|
||||
Map<String, String> 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(), false)) {
|
||||
try (Response response = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", (RequestBody) null, headers, NO_HANDLER, SealedSenderAccess.NONE, false)) {
|
||||
validateServiceResponse(response);
|
||||
|
||||
List<SignalServiceEnvelopeEntity> envelopes = readBodyJson(response.body(), SignalServiceEnvelopeEntityList.class).getMessages();
|
||||
|
@ -870,18 +872,18 @@ public class PushServiceSocket {
|
|||
* for all devices. If it is not a primary, it will only contain the prekeys for that specific device.
|
||||
*/
|
||||
public List<PreKeyBundle> getPreKeys(SignalServiceAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
int deviceId)
|
||||
throws IOException
|
||||
{
|
||||
return getPreKeysBySpecifier(destination, unidentifiedAccess, deviceId == 1 ? "*" : String.valueOf(deviceId));
|
||||
return getPreKeysBySpecifier(destination, sealedSenderAccess, deviceId == 1 ? "*" : String.valueOf(deviceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a prekey for a specific device.
|
||||
*/
|
||||
public PreKeyBundle getPreKey(SignalServiceAddress destination, int deviceId) throws IOException {
|
||||
List<PreKeyBundle> bundles = getPreKeysBySpecifier(destination, Optional.empty(), String.valueOf(deviceId));
|
||||
List<PreKeyBundle> bundles = getPreKeysBySpecifier(destination, null, String.valueOf(deviceId));
|
||||
|
||||
if (bundles.size() > 0) {
|
||||
return bundles.get(0);
|
||||
|
@ -891,7 +893,7 @@ public class PushServiceSocket {
|
|||
}
|
||||
|
||||
private List<PreKeyBundle> getPreKeysBySpecifier(SignalServiceAddress destination,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
String deviceSpecifier)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -900,7 +902,7 @@ public class PushServiceSocket {
|
|||
|
||||
Log.d(TAG, "Fetching prekeys for " + destination.getIdentifier() + "." + deviceSpecifier + ", i.e. GET " + path);
|
||||
|
||||
String responseText = makeServiceRequest(path, "GET", null, NO_HEADERS, NO_HANDLER, unidentifiedAccess);
|
||||
String responseText = makeServiceRequest(path, "GET", null, NO_HEADERS, NO_HANDLER, sealedSenderAccess);
|
||||
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
|
||||
List<PreKeyBundle> bundles = new LinkedList<>();
|
||||
|
||||
|
@ -958,7 +960,7 @@ public class PushServiceSocket {
|
|||
if (responseCode == 409) {
|
||||
throw new NonSuccessfulResponseCodeException(409);
|
||||
}
|
||||
}, Optional.empty());
|
||||
}, null);
|
||||
}
|
||||
|
||||
public void retrieveBackup(int cdnNumber, Map<String, String> headers, String cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
|
||||
|
@ -1012,8 +1014,8 @@ public class PushServiceSocket {
|
|||
return output.toByteArray();
|
||||
}
|
||||
|
||||
public ListenableFuture<SignalServiceProfile> retrieveProfile(SignalServiceAddress target, Optional<UnidentifiedAccess> unidentifiedAccess, Locale locale) {
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, target.getIdentifier()), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), unidentifiedAccess);
|
||||
public ListenableFuture<SignalServiceProfile> retrieveProfile(SignalServiceAddress target, @Nullable SealedSenderAccess sealedSenderAccess, Locale locale) {
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, target.getIdentifier()), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), sealedSenderAccess);
|
||||
|
||||
return FutureTransformers.map(response, body -> {
|
||||
try {
|
||||
|
@ -1025,7 +1027,7 @@ public class PushServiceSocket {
|
|||
});
|
||||
}
|
||||
|
||||
public ListenableFuture<ProfileAndCredential> retrieveVersionedProfileAndCredential(ACI target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess, Locale locale) {
|
||||
public ListenableFuture<ProfileAndCredential> retrieveVersionedProfileAndCredential(ACI target, ProfileKey profileKey, @Nullable SealedSenderAccess sealedSenderAccess, Locale locale) {
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target.getLibSignalAci());
|
||||
ProfileKeyCredentialRequestContext requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, target.getLibSignalAci(), profileKey);
|
||||
ProfileKeyCredentialRequest request = requestContext.getRequest();
|
||||
|
@ -1035,7 +1037,7 @@ public class PushServiceSocket {
|
|||
String subPath = String.format("%s/%s/%s?credentialType=expiringProfileKey", target, version, credentialRequest);
|
||||
|
||||
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), unidentifiedAccess);
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), sealedSenderAccess);
|
||||
|
||||
return FutureTransformers.map(response, body -> formatProfileAndCredentialBody(requestContext, body));
|
||||
}
|
||||
|
@ -1061,12 +1063,12 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public ListenableFuture<SignalServiceProfile> retrieveVersionedProfile(ACI target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess, Locale locale) {
|
||||
public ListenableFuture<SignalServiceProfile> retrieveVersionedProfile(ACI target, ProfileKey profileKey, @Nullable SealedSenderAccess sealedSenderAccess, Locale locale) {
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target.getLibSignalAci());
|
||||
|
||||
String version = profileKeyIdentifier.serialize();
|
||||
String subPath = String.format("%s/%s", target, version);
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), unidentifiedAccess);
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), sealedSenderAccess);
|
||||
|
||||
return FutureTransformers.map(response, body -> {
|
||||
try {
|
||||
|
@ -1102,7 +1104,7 @@ public class PushServiceSocket {
|
|||
requestBody,
|
||||
NO_HEADERS,
|
||||
PaymentsRegionException::responseCodeHandler,
|
||||
Optional.empty());
|
||||
SealedSenderAccess.NONE);
|
||||
|
||||
if (signalServiceProfileWrite.hasAvatar() && profileAvatar != null) {
|
||||
try {
|
||||
|
@ -1126,13 +1128,12 @@ public class PushServiceSocket {
|
|||
}
|
||||
|
||||
public Single<ServiceResponse<IdentityCheckResponse>> performIdentityCheck(@Nonnull IdentityCheckRequest request,
|
||||
@Nonnull Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nonnull ResponseMapper<IdentityCheckResponse> responseMapper)
|
||||
{
|
||||
Single<ServiceResponse<IdentityCheckResponse>> requestSingle = Single.fromCallable(() -> {
|
||||
try (Response response = getServiceConnection(PROFILE_BATCH_CHECK_PATH, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), unidentifiedAccess, false)) {
|
||||
try (Response response = getServiceConnection(PROFILE_BATCH_CHECK_PATH, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), SealedSenderAccess.NONE, false)) {
|
||||
String body = response.body() != null ? readBodyString(response.body()): "";
|
||||
return responseMapper.map(response.code(), body, response::header, unidentifiedAccess.isPresent());
|
||||
return responseMapper.map(response.code(), body, response::header, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1146,7 +1147,7 @@ public class PushServiceSocket {
|
|||
@Nonnull ResponseMapper<BackupV2AuthCheckResponse> responseMapper)
|
||||
{
|
||||
Single<ServiceResponse<BackupV2AuthCheckResponse>> requestSingle = Single.fromCallable(() -> {
|
||||
try (Response response = getServiceConnection(BACKUP_AUTH_CHECK_V2, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), Optional.empty(), false)) {
|
||||
try (Response response = getServiceConnection(BACKUP_AUTH_CHECK_V2, "POST", jsonRequestBody(JsonUtil.toJson(request)), Collections.emptyMap(), SealedSenderAccess.NONE, false)) {
|
||||
String body = response.body() != null ? readBodyString(response.body()): "";
|
||||
return responseMapper.map(response.code(), body, response::header, false);
|
||||
}
|
||||
|
@ -1159,12 +1160,12 @@ public class PushServiceSocket {
|
|||
}
|
||||
|
||||
public BackupV2AuthCheckResponse checkSvr2AuthCredentials(@Nullable String number, @Nonnull List<String> passwords) throws IOException {
|
||||
String response = makeServiceRequest(BACKUP_AUTH_CHECK_V2, "POST", JsonUtil.toJson(new BackupAuthCheckRequest(number, passwords)), NO_HEADERS, UNOPINIONATED_HANDLER, Optional.empty());
|
||||
String response = makeServiceRequest(BACKUP_AUTH_CHECK_V2, "POST", JsonUtil.toJson(new BackupAuthCheckRequest(number, passwords)), NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
return JsonUtil.fromJson(response, BackupV2AuthCheckResponse.class);
|
||||
}
|
||||
|
||||
public BackupV3AuthCheckResponse checkSvr3AuthCredentials(@Nullable String number, @Nonnull List<String> passwords) throws IOException {
|
||||
String response = makeServiceRequest(BACKUP_AUTH_CHECK_V3, "POST", JsonUtil.toJson(new BackupAuthCheckRequest(number, passwords)), NO_HEADERS, UNOPINIONATED_HANDLER, Optional.empty());
|
||||
String response = makeServiceRequest(BACKUP_AUTH_CHECK_V3, "POST", JsonUtil.toJson(new BackupAuthCheckRequest(number, passwords)), NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
return JsonUtil.fromJson(response, BackupV3AuthCheckResponse.class);
|
||||
}
|
||||
|
||||
|
@ -1218,7 +1219,7 @@ public class PushServiceSocket {
|
|||
case 422: throw new UsernameMalformedException();
|
||||
case 409: throw new UsernameTakenException();
|
||||
}
|
||||
}, Optional.empty());
|
||||
}, SealedSenderAccess.NONE);
|
||||
|
||||
return JsonUtil.fromJsonResponse(responseString, ReserveUsernameResponse.class);
|
||||
}
|
||||
|
@ -1249,7 +1250,7 @@ public class PushServiceSocket {
|
|||
case 410:
|
||||
throw new UsernameTakenException();
|
||||
}
|
||||
}, Optional.empty());
|
||||
}, SealedSenderAccess.NONE);
|
||||
|
||||
return JsonUtil.fromJson(response, ConfirmUsernameResponse.class).getUsernameLinkHandle();
|
||||
} catch (BaseUsernameException e) {
|
||||
|
@ -1303,7 +1304,7 @@ public class PushServiceSocket {
|
|||
if (responseCode == 428) {
|
||||
throw new CaptchaRejectedException();
|
||||
}
|
||||
}, Optional.empty());
|
||||
}, SealedSenderAccess.NONE);
|
||||
|
||||
}
|
||||
|
||||
|
@ -2139,7 +2140,7 @@ public class PushServiceSocket {
|
|||
private String makeServiceRequestWithoutAuthentication(String urlFragment, String method, String jsonBody, Map<String, String> headers, ResponseCodeHandler responseCodeHandler)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, MalformedResponseException
|
||||
{
|
||||
try (Response response = makeServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, responseCodeHandler, Optional.empty(), true)) {
|
||||
try (Response response = makeServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, responseCodeHandler, SealedSenderAccess.NONE, true)) {
|
||||
return readBodyString(response);
|
||||
}
|
||||
}
|
||||
|
@ -2147,13 +2148,13 @@ public class PushServiceSocket {
|
|||
private String makeServiceRequest(String urlFragment, String method, String jsonBody)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, MalformedResponseException
|
||||
{
|
||||
return makeServiceRequest(urlFragment, method, jsonBody, NO_HEADERS, NO_HANDLER, Optional.empty());
|
||||
return makeServiceRequest(urlFragment, method, jsonBody, NO_HEADERS, NO_HANDLER, SealedSenderAccess.NONE);
|
||||
}
|
||||
|
||||
private String makeServiceRequest(String urlFragment, String method, String jsonBody, Map<String, String> headers, ResponseCodeHandler responseCodeHandler, Optional<UnidentifiedAccess> unidentifiedAccessKey)
|
||||
private String makeServiceRequest(String urlFragment, String method, String jsonBody, Map<String, String> headers, ResponseCodeHandler responseCodeHandler, @Nullable SealedSenderAccess sealedSenderAccess)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, MalformedResponseException
|
||||
{
|
||||
try (Response response = makeServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, responseCodeHandler, unidentifiedAccessKey, false)) {
|
||||
try (Response response = makeServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, responseCodeHandler, sealedSenderAccess, false)) {
|
||||
return readBodyString(response);
|
||||
}
|
||||
}
|
||||
|
@ -2172,10 +2173,10 @@ public class PushServiceSocket {
|
|||
String method,
|
||||
String jsonBody,
|
||||
Map<String, String> headers,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccessKey)
|
||||
@Nullable SealedSenderAccess sealedSenderAccess)
|
||||
{
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(unidentifiedAccessKey.isPresent());
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, unidentifiedAccessKey, false));
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(sealedSenderAccess != null);
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, sealedSenderAccess, false));
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(call);
|
||||
|
@ -2208,13 +2209,13 @@ public class PushServiceSocket {
|
|||
RequestBody body,
|
||||
Map<String, String> headers,
|
||||
ResponseCodeHandler responseCodeHandler,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccessKey,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
boolean doNotAddAuthenticationOrUnidentifiedAccessKey)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, MalformedResponseException
|
||||
{
|
||||
Response response = null;
|
||||
try {
|
||||
response = getServiceConnection(urlFragment, method, body, headers, unidentifiedAccessKey, doNotAddAuthenticationOrUnidentifiedAccessKey);
|
||||
response = getServiceConnection(urlFragment, method, body, headers, sealedSenderAccess, doNotAddAuthenticationOrUnidentifiedAccessKey);
|
||||
responseCodeHandler.handle(response.code(), response.body());
|
||||
return validateServiceResponse(response);
|
||||
} catch (Exception e) {
|
||||
|
@ -2288,13 +2289,13 @@ public class PushServiceSocket {
|
|||
String method,
|
||||
RequestBody body,
|
||||
Map<String, String> headers,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
boolean doNotAddAuthenticationOrUnidentifiedAccessKey)
|
||||
throws PushNetworkException
|
||||
{
|
||||
try {
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(unidentifiedAccess.isPresent());
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, body, headers, unidentifiedAccess, doNotAddAuthenticationOrUnidentifiedAccessKey));
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(sealedSenderAccess != null);
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, body, headers, sealedSenderAccess, doNotAddAuthenticationOrUnidentifiedAccessKey));
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(call);
|
||||
|
@ -2327,14 +2328,11 @@ public class PushServiceSocket {
|
|||
String method,
|
||||
RequestBody body,
|
||||
Map<String, String> headers,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
@Nullable SealedSenderAccess sealedSenderAccess,
|
||||
boolean doNotAddAuthenticationOrUnidentifiedAccessKey) {
|
||||
|
||||
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
|
||||
|
||||
// Log.d(TAG, "Push service URL: " + connectionHolder.getUrl());
|
||||
// Log.d(TAG, "Opening URL: " + String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
|
||||
Request.Builder request = new Request.Builder();
|
||||
request.url(String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
request.method(method, body);
|
||||
|
@ -2344,8 +2342,8 @@ public class PushServiceSocket {
|
|||
}
|
||||
|
||||
if (!headers.containsKey("Authorization") && !doNotAddAuthenticationOrUnidentifiedAccessKey) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
request.addHeader("Unidentified-Access-Key", Base64.encodeWithPadding(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
if (sealedSenderAccess != null) {
|
||||
request.addHeader(sealedSenderAccess.getHeaderName(), sealedSenderAccess.getHeaderValue());
|
||||
} else if (credentialsProvider.getPassword() != null) {
|
||||
request.addHeader("Authorization", getAuthorizationHeader(credentialsProvider));
|
||||
}
|
||||
|
@ -2364,6 +2362,12 @@ public class PushServiceSocket {
|
|||
|
||||
private Response makeStorageRequest(String authorization, String path, String method, RequestBody body, ResponseCodeHandler responseCodeHandler)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
return makeStorageRequest(authorization, path, method, body, NO_HEADERS, responseCodeHandler);
|
||||
}
|
||||
|
||||
private Response makeStorageRequest(String authorization, String path, String method, RequestBody body, Map<String, String> headers, ResponseCodeHandler responseCodeHandler)
|
||||
throws PushNetworkException, NonSuccessfulResponseCodeException
|
||||
{
|
||||
ConnectionHolder connectionHolder = getRandom(storageClients, random);
|
||||
OkHttpClient okHttpClient = connectionHolder.getClient()
|
||||
|
@ -2372,8 +2376,6 @@ public class PushServiceSocket {
|
|||
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
// Log.d(TAG, "Opening URL: " + connectionHolder.getUrl());
|
||||
|
||||
Request.Builder request = new Request.Builder().url(connectionHolder.getUrl() + path);
|
||||
request.method(method, body);
|
||||
|
||||
|
@ -2385,6 +2387,10 @@ public class PushServiceSocket {
|
|||
request.addHeader("Authorization", authorization);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
request.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
Call call = okHttpClient.newCall(request.build());
|
||||
|
||||
synchronized (connections) {
|
||||
|
@ -2784,7 +2790,7 @@ public class PushServiceSocket {
|
|||
null,
|
||||
NO_HEADERS,
|
||||
NO_HANDLER,
|
||||
Optional.empty());
|
||||
SealedSenderAccess.NONE);
|
||||
|
||||
return JsonUtil.fromJson(response, CredentialResponse.class);
|
||||
}
|
||||
|
@ -2816,8 +2822,8 @@ public class PushServiceSocket {
|
|||
}
|
||||
};
|
||||
|
||||
public void putNewGroupsV2Group(Group group, GroupsV2AuthorizationString authorization)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
public GroupResponse putNewGroupsV2Group(Group group, GroupsV2AuthorizationString authorization)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, IOException, MalformedResponseException
|
||||
{
|
||||
try (Response response = makeStorageRequest(authorization.toString(),
|
||||
GROUPSV2_GROUP,
|
||||
|
@ -2825,11 +2831,11 @@ public class PushServiceSocket {
|
|||
protobufRequestBody(group),
|
||||
GROUPS_V2_PUT_RESPONSE_HANDLER))
|
||||
{
|
||||
return;
|
||||
return GroupResponse.ADAPTER.decode(readBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
public Group getGroupsV2Group(GroupsV2AuthorizationString authorization)
|
||||
public GroupResponse getGroupsV2Group(GroupsV2AuthorizationString authorization)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, IOException, MalformedResponseException
|
||||
{
|
||||
try (Response response = makeStorageRequest(authorization.toString(),
|
||||
|
@ -2838,7 +2844,7 @@ public class PushServiceSocket {
|
|||
null,
|
||||
GROUPS_V2_GET_CURRENT_HANDLER))
|
||||
{
|
||||
return Group.ADAPTER.decode(readBodyBytes(response));
|
||||
return GroupResponse.ADAPTER.decode(readBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2855,7 +2861,7 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
public GroupChange patchGroupsV2Group(GroupChange.Actions groupChange, String authorization, Optional<byte[]> groupLinkPassword)
|
||||
public GroupChangeResponse patchGroupsV2Group(GroupChange.Actions groupChange, String authorization, Optional<byte[]> groupLinkPassword)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, IOException, MalformedResponseException
|
||||
{
|
||||
String path;
|
||||
|
@ -2872,17 +2878,21 @@ public class PushServiceSocket {
|
|||
protobufRequestBody(groupChange),
|
||||
GROUPS_V2_PATCH_RESPONSE_HANDLER))
|
||||
{
|
||||
return GroupChange.ADAPTER.decode(readBodyBytes(response));
|
||||
return GroupChangeResponse.ADAPTER.decode(readBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
public GroupHistory getGroupsV2GroupHistory(int fromVersion, GroupsV2AuthorizationString authorization, int highestKnownEpoch, boolean includeFirstState)
|
||||
public GroupHistory getGroupHistory(int fromVersion, GroupsV2AuthorizationString authorization, int highestKnownEpoch, boolean includeFirstState, long sendEndorsementsExpirationMs)
|
||||
throws IOException
|
||||
{
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Cached-Send-Endorsements", Long.toString(TimeUnit.MILLISECONDS.toSeconds(sendEndorsementsExpirationMs)));
|
||||
|
||||
try (Response response = makeStorageRequest(authorization.toString(),
|
||||
String.format(Locale.US, GROUPSV2_GROUP_CHANGES, fromVersion, highestKnownEpoch, includeFirstState),
|
||||
"GET",
|
||||
null,
|
||||
headers,
|
||||
GROUPS_V2_GET_LOGS_HANDLER))
|
||||
{
|
||||
|
||||
|
@ -2890,12 +2900,7 @@ public class PushServiceSocket {
|
|||
throw new PushNetworkException("No body!");
|
||||
}
|
||||
|
||||
GroupChanges groupChanges;
|
||||
try (InputStream input = response.body().byteStream()) {
|
||||
groupChanges = GroupChanges.ADAPTER.decode(input);
|
||||
} catch (IOException e) {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
GroupChanges groupChanges = GroupChanges.ADAPTER.decode(readBodyBytes(response));
|
||||
|
||||
if (response.code() == 206) {
|
||||
String contentRangeHeader = response.header("Content-Range");
|
||||
|
|
|
@ -213,13 +213,24 @@ message GroupChange {
|
|||
uint32 changeEpoch = 3;
|
||||
}
|
||||
|
||||
message GroupResponse {
|
||||
Group group = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupChanges {
|
||||
message GroupChangeState {
|
||||
GroupChange groupChange = 1;
|
||||
Group groupState = 2;
|
||||
}
|
||||
|
||||
repeated GroupChangeState groupChanges = 1;
|
||||
repeated GroupChangeState groupChanges = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupChangeResponse {
|
||||
GroupChange groupChange = 1;
|
||||
bytes groupSendEndorsementsResponse = 2;
|
||||
}
|
||||
|
||||
message GroupAttributeBlob {
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
|
|||
import org.whispersystems.signalservice.api.SignalSessionLock
|
||||
import org.whispersystems.signalservice.api.crypto.ContentHint
|
||||
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
|
||||
import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
|
||||
|
@ -93,7 +94,7 @@ class SignalClient {
|
|||
|
||||
val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
|
||||
SignalProtocolAddress(to.aci.toString(), 1),
|
||||
Optional.empty(),
|
||||
SealedSenderAccess.NONE,
|
||||
EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
|
||||
)
|
||||
|
||||
|
@ -124,7 +125,7 @@ class SignalClient {
|
|||
|
||||
val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
|
||||
SignalProtocolAddress(to.aci.toString(), 1),
|
||||
Optional.of(UnidentifiedAccess(to.unidentifiedAccessKey, senderCertificate.serialized, false)),
|
||||
SealedSenderAccess.forIndividual(UnidentifiedAccess(to.unidentifiedAccessKey, senderCertificate.serialized, false)),
|
||||
EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue