mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 02:39:55 +01:00
Incrementally insert MSL entries for legacy group sends.
This commit is contained in:
committed by
Alex Hart
parent
acc825971b
commit
7f0a0bef5a
@@ -10,6 +10,8 @@ import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessageSendLogDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
@@ -33,6 +35,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.http.CancelationSignal;
|
||||
import org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -45,6 +48,8 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class GroupSendUtil {
|
||||
@@ -62,19 +67,19 @@ public final class GroupSendUtil {
|
||||
*
|
||||
* Messages sent this way, if failed to be decrypted by the receiving party, can be requested to be resent.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
public static List<SendMessageResult> sendResendableDataMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
long relatedMessageId,
|
||||
boolean isRelatedMessageMms,
|
||||
@NonNull MessageId messageId,
|
||||
@NonNull SignalServiceDataMessage message)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
return sendMessage(context, groupId, allTargets, isRecipientUpdate, DataSendOperation.resendable(message, contentHint, relatedMessageId, isRelatedMessageMms), null);
|
||||
return sendMessage(context, groupId, allTargets, isRecipientUpdate, DataSendOperation.resendable(message, contentHint, messageId), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,11 +88,12 @@ public final class GroupSendUtil {
|
||||
*
|
||||
* Messages sent this way, if failed to be decrypted by the receiving party, can *not* be requested to be resent.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static List<SendMessageResult> sendUnresendableDataMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
ContentHint contentHint,
|
||||
@@ -100,10 +106,12 @@ public final class GroupSendUtil {
|
||||
/**
|
||||
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
|
||||
* {@link SendMessageResult}s just like we're used to.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static List<SendMessageResult> sendTypingMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
@NonNull SignalServiceTypingMessage message,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
@@ -116,11 +124,12 @@ public final class GroupSendUtil {
|
||||
* Handles all of the logic of sending to a group. Will do sender key sends and legacy 1:1 sends as-needed, and give you back a list of
|
||||
* {@link SendMessageResult}s just like we're used to.
|
||||
*
|
||||
* @param groupId The groupId of the group you're sending to, or null if you're sending to a collection of recipients not joined by a group.
|
||||
* @param isRecipientUpdate True if you've already sent this message to some recipients in the past, otherwise false.
|
||||
*/
|
||||
@WorkerThread
|
||||
private static List<SendMessageResult> sendMessage(@NonNull Context context,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
@Nullable GroupId.V2 groupId,
|
||||
@NonNull List<Recipient> allTargets,
|
||||
boolean isRecipientUpdate,
|
||||
@NonNull SendOperation sendOperation,
|
||||
@@ -147,7 +156,11 @@ public final class GroupSendUtil {
|
||||
}
|
||||
|
||||
if (FeatureFlags.senderKey()) {
|
||||
if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
|
||||
if (groupId == null) {
|
||||
Log.i(TAG, "Recipients not in a group. Using legacy.");
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
senderKeyTargets.clear();
|
||||
} else if (Recipient.self().getSenderKeyCapability() != Recipient.Capability.SUPPORTED) {
|
||||
Log.i(TAG, "All of our devices do not support sender key. Using legacy.");
|
||||
legacyTargets.addAll(senderKeyTargets);
|
||||
senderKeyTargets.clear();
|
||||
@@ -168,11 +181,11 @@ public final class GroupSendUtil {
|
||||
|
||||
List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
|
||||
|
||||
if (senderKeyTargets.size() > 0) {
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
if (senderKeyTargets.size() > 0 && groupId != null) {
|
||||
DistributionId distributionId = DatabaseFactory.getGroupDatabase(context).getOrCreateDistributionId(groupId);
|
||||
long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
|
||||
long keyAge = System.currentTimeMillis() - keyCreateTime;
|
||||
|
||||
if (keyCreateTime != -1 && keyAge > MAX_KEY_AGE) {
|
||||
Log.w(TAG, "Key is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
|
||||
@@ -190,7 +203,7 @@ public final class GroupSendUtil {
|
||||
Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
|
||||
|
||||
if (sendOperation.shouldIncludeInMessageLog()) {
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
|
||||
}
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, "No session. Falling back to legacy sends.", e);
|
||||
@@ -212,16 +225,29 @@ public final class GroupSendUtil {
|
||||
List<Optional<UnidentifiedAccessPair>> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList());
|
||||
boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0;
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, cancelationSignal);
|
||||
|
||||
final MessageSendLogDatabase messageLogDatabase = DatabaseFactory.getMessageLogDatabase(context);
|
||||
final AtomicLong entryId = new AtomicLong(-1);
|
||||
final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog();
|
||||
|
||||
List<SendMessageResult> results = sendOperation.sendLegacy(messageSender, targets, access, recipientUpdate, result -> {
|
||||
if (!includeInMessageLog) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (entryId) {
|
||||
if (entryId.get() == -1) {
|
||||
entryId.set(messageLogDatabase.insertIfPossible(recipients.requireRecipientId(result.getAddress()), sendOperation.getSentTimestamp(), result, sendOperation.getContentHint(), sendOperation.getRelatedMessageId()));
|
||||
} else {
|
||||
messageLogDatabase.addRecipientToExistingEntryIfPossible(entryId.get(), recipients.requireRecipientId(result.getAddress()), result);
|
||||
}
|
||||
}
|
||||
}, cancelationSignal);
|
||||
|
||||
allResults.addAll(results);
|
||||
|
||||
int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
|
||||
Log.d(TAG, "Successfully using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
|
||||
|
||||
if (sendOperation.shouldIncludeInMessageLog()) {
|
||||
DatabaseFactory.getMessageLogDatabase(context).insertIfPossible(sendOperation.getSentTimestamp(), legacyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId(), sendOperation.isRelatedMessageMms());
|
||||
}
|
||||
}
|
||||
|
||||
return allResults;
|
||||
@@ -240,37 +266,39 @@ public final class GroupSendUtil {
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException;
|
||||
|
||||
@NonNull ContentHint getContentHint();
|
||||
long getSentTimestamp();
|
||||
boolean shouldIncludeInMessageLog();
|
||||
long getRelatedMessageId();
|
||||
boolean isRelatedMessageMms();
|
||||
@NonNull MessageId getRelatedMessageId();
|
||||
}
|
||||
|
||||
private static class DataSendOperation implements SendOperation {
|
||||
private final SignalServiceDataMessage message;
|
||||
private final ContentHint contentHint;
|
||||
private final long relatedMessageId;
|
||||
private final boolean isRelatedMessageMms;
|
||||
private final MessageId relatedMessageId;
|
||||
private final boolean resendable;
|
||||
|
||||
public static DataSendOperation resendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, long relatedMessageId, boolean isRelatedMessageMms) {
|
||||
return new DataSendOperation(message, contentHint, true, relatedMessageId, isRelatedMessageMms);
|
||||
public static DataSendOperation resendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, @NonNull MessageId relatedMessageId) {
|
||||
return new DataSendOperation(message, contentHint, true, relatedMessageId);
|
||||
}
|
||||
|
||||
public static DataSendOperation unresendable(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint) {
|
||||
return new DataSendOperation(message, contentHint, false, -1, false);
|
||||
return new DataSendOperation(message, contentHint, false, null);
|
||||
}
|
||||
|
||||
private DataSendOperation(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, boolean resendable, long relatedMessageId, boolean isRelatedMessageMms) {
|
||||
private DataSendOperation(@NonNull SignalServiceDataMessage message, @NonNull ContentHint contentHint, boolean resendable, @Nullable MessageId relatedMessageId) {
|
||||
this.message = message;
|
||||
this.contentHint = contentHint;
|
||||
this.resendable = resendable;
|
||||
this.relatedMessageId = relatedMessageId;
|
||||
this.isRelatedMessageMms = isRelatedMessageMms;
|
||||
|
||||
if (resendable && relatedMessageId == null) {
|
||||
throw new IllegalArgumentException("If a message is resendable, it must have a related message ID!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,10 +317,11 @@ public final class GroupSendUtil {
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException, UntrustedIdentityException
|
||||
{
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message);
|
||||
return messageSender.sendDataMessage(targets, access, isRecipientUpdate, contentHint, message, partialListener, cancelationSignal);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,13 +340,12 @@ public final class GroupSendUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRelatedMessageId() {
|
||||
return relatedMessageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelatedMessageMms() {
|
||||
return isRelatedMessageMms;
|
||||
public @NonNull MessageId getRelatedMessageId() {
|
||||
if (relatedMessageId != null) {
|
||||
return relatedMessageId;
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +374,7 @@ public final class GroupSendUtil {
|
||||
@NonNull List<SignalServiceAddress> targets,
|
||||
@NonNull List<Optional<UnidentifiedAccessPair>> access,
|
||||
boolean isRecipientUpdate,
|
||||
@Nullable PartialSendCompleteListener partialListener,
|
||||
@Nullable CancelationSignal cancelationSignal)
|
||||
throws IOException
|
||||
{
|
||||
@@ -369,13 +398,8 @@ public final class GroupSendUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRelatedMessageId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelatedMessageMms() {
|
||||
return false;
|
||||
public @NonNull MessageId getRelatedMessageId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,10 +410,23 @@ public final class GroupSendUtil {
|
||||
|
||||
private final Map<RecipientId, Optional<UnidentifiedAccessPair>> accessById;
|
||||
private final Map<RecipientId, SignalServiceAddress> addressById;
|
||||
private final Map<UUID, RecipientId> idByUuid;
|
||||
private final Map<String, RecipientId> idByE164;
|
||||
|
||||
RecipientData(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
this.accessById = UnidentifiedAccessUtil.getAccessMapFor(context, recipients);
|
||||
this.addressById = mapAddresses(context, recipients);
|
||||
this.idByUuid = new HashMap<>(recipients.size());
|
||||
this.idByE164 = new HashMap<>(recipients.size());
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
if (recipient.hasUuid()) {
|
||||
idByUuid.put(recipient.requireUuid(), recipient.getId());
|
||||
}
|
||||
if (recipient.hasE164()) {
|
||||
idByE164.put(recipient.requireE164(), recipient.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull SignalServiceAddress getAddress(@NonNull RecipientId id) {
|
||||
@@ -404,6 +441,16 @@ public final class GroupSendUtil {
|
||||
return Objects.requireNonNull(accessById.get(id)).get().getTargetUnidentifiedAccess().get();
|
||||
}
|
||||
|
||||
@NonNull RecipientId requireRecipientId(@NonNull SignalServiceAddress address) {
|
||||
if (address.getUuid().isPresent() && idByUuid.containsKey(address.getUuid().get())) {
|
||||
return Objects.requireNonNull(idByUuid.get(address.getUuid().get()));
|
||||
} else if (address.getNumber().isPresent() && idByE164.containsKey(address.getNumber().get())) {
|
||||
return Objects.requireNonNull(idByE164.get(address.getNumber().get()));
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull Map<RecipientId, SignalServiceAddress> mapAddresses(@NonNull Context context, @NonNull List<Recipient> recipients) throws IOException {
|
||||
List<SignalServiceAddress> addresses = RecipientUtil.toSignalServiceAddressesFromResolved(context, recipients);
|
||||
|
||||
|
||||
@@ -287,8 +287,8 @@ public final class MessageContentProcessor {
|
||||
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false))
|
||||
.then(GroupV2UpdateSelfProfileKeyJob.withQueueLimits(groupId.get().requireV2()))
|
||||
.enqueue();
|
||||
} else if (!sender.isPushV2Group()) {
|
||||
Log.i(TAG, "Message was to a 1:1 or GV1 chat. Ensuring this user has our profile key.");
|
||||
} else if (!sender.isGroup()) {
|
||||
Log.i(TAG, "Message was to a 1:1. Ensuring this user has our profile key.");
|
||||
ApplicationDependencies.getJobManager().startChain(new RefreshAttributesJob(false))
|
||||
.then(ProfileKeySendJob.create(context, DatabaseFactory.getThreadDatabase(context).getThreadIdFor(sender), true))
|
||||
.enqueue();
|
||||
|
||||
Reference in New Issue
Block a user