Search in sources :

Example 1 with MessageSendLogDatabase

use of org.thoughtcrime.securesms.database.MessageSendLogDatabase in project Signal-Android by WhisperSystems.

the class GroupSendUtil method sendMessage.

/**
 * 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, @Nullable GroupId.V2 groupId, @Nullable MessageId relatedMessageId, @NonNull List<Recipient> allTargets, boolean isRecipientUpdate, @NonNull SendOperation sendOperation, @Nullable CancelationSignal cancelationSignal) throws IOException, UntrustedIdentityException {
    Log.i(TAG, "Starting group send. GroupId: " + (groupId != null ? groupId.toString() : "none") + ", RelatedMessageId: " + (relatedMessageId != null ? relatedMessageId.toString() : "none") + ", Targets: " + allTargets.size() + ", RecipientUpdate: " + isRecipientUpdate + ", Operation: " + sendOperation.getClass().getSimpleName());
    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);
    Optional<GroupRecord> groupRecord = groupId != null ? SignalDatabase.groups().getGroup(groupId) : Optional.absent();
    List<Recipient> senderKeyTargets = new LinkedList<>();
    List<Recipient> legacyTargets = new LinkedList<>();
    for (Recipient recipient : registeredTargets) {
        Optional<UnidentifiedAccessPair> access = recipients.getAccessPair(recipient.getId());
        boolean validMembership = groupRecord.isPresent() && groupRecord.get().getMembers().contains(recipient.getId());
        if (recipient.getSenderKeyCapability() == Recipient.Capability.SUPPORTED && recipient.hasServiceId() && access.isPresent() && access.get().getTargetUnidentifiedAccess().isPresent() && validMembership) {
            senderKeyTargets.add(recipient);
        } else {
            legacyTargets.add(recipient);
        }
    }
    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();
    } else if (SignalStore.internalValues().removeSenderKeyMinimum()) {
        Log.i(TAG, "Sender key minimum removed. Using for " + senderKeyTargets.size() + " recipients.");
    } else if (senderKeyTargets.size() < 2) {
        Log.i(TAG, "Too few sender-key-capable users (" + senderKeyTargets.size() + "). Doing all legacy sends.");
        legacyTargets.addAll(senderKeyTargets);
        senderKeyTargets.clear();
    } else {
        Log.i(TAG, "Can use sender key for " + senderKeyTargets.size() + "/" + allTargets.size() + " recipients.");
    }
    if (relatedMessageId != null) {
        SignalLocalMetrics.GroupMessageSend.onSenderKeyStarted(relatedMessageId.getId());
    }
    List<SendMessageResult> allResults = new ArrayList<>(allTargets.size());
    SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
    if (senderKeyTargets.size() > 0 && groupId != null) {
        DistributionId distributionId = SignalDatabase.groups().getOrCreateDistributionId(groupId);
        long keyCreateTime = SenderKeyUtil.getCreateTimeForOurKey(context, distributionId);
        long keyAge = System.currentTimeMillis() - keyCreateTime;
        if (keyCreateTime != -1 && keyAge > FeatureFlags.senderKeyMaxAge()) {
            Log.w(TAG, "DistributionId " + distributionId + " was created at " + keyCreateTime + " and is " + (keyAge) + " ms old (~" + TimeUnit.MILLISECONDS.toDays(keyAge) + " days). Rotating.");
            SenderKeyUtil.rotateOurKey(context, distributionId);
        }
        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<SendMessageResult> results = sendOperation.sendWithSenderKey(messageSender, distributionId, targets, access, isRecipientUpdate);
            allResults.addAll(results);
            int successCount = (int) results.stream().filter(SendMessageResult::isSuccess).count();
            Log.d(TAG, "Successfully sent using sender key to " + successCount + "/" + targets.size() + " sender key targets.");
            if (sendOperation.shouldIncludeInMessageLog()) {
                SignalDatabase.messageLog().insertIfPossible(sendOperation.getSentTimestamp(), senderKeyTargets, results, sendOperation.getContentHint(), sendOperation.getRelatedMessageId());
            }
            if (relatedMessageId != null) {
                SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
            }
        } catch (InvalidUnidentifiedAccessHeaderException e) {
            Log.w(TAG, "Someone had a bad UD header. Falling back to legacy sends.", e);
            legacyTargets.addAll(senderKeyTargets);
        } catch (NoSessionException e) {
            Log.w(TAG, "No session. Falling back to legacy sends.", e);
            legacyTargets.addAll(senderKeyTargets);
        } catch (InvalidKeyException e) {
            Log.w(TAG, "Invalid key. Falling back to legacy sends.", e);
            legacyTargets.addAll(senderKeyTargets);
        } catch (InvalidRegistrationIdException e) {
            Log.w(TAG, "Invalid registrationId. Falling back to legacy sends.", e);
            legacyTargets.addAll(senderKeyTargets);
        } catch (NotFoundException e) {
            Log.w(TAG, "Someone was unregistered. Falling back to legacy sends.", e);
            legacyTargets.addAll(senderKeyTargets);
        }
    } else if (relatedMessageId != null) {
        SignalLocalMetrics.GroupMessageSend.onSenderKeyShared(relatedMessageId.getId());
        SignalLocalMetrics.GroupMessageSend.onSenderKeyEncrypted(relatedMessageId.getId());
        SignalLocalMetrics.GroupMessageSend.onSenderKeyMessageSent(relatedMessageId.getId());
        SignalLocalMetrics.GroupMessageSend.onSenderKeySyncSent(relatedMessageId.getId());
        SignalLocalMetrics.GroupMessageSend.onSenderKeyMslInserted(relatedMessageId.getId());
    }
    if (cancelationSignal != null && cancelationSignal.isCanceled()) {
        throw new CancelationException();
    }
    boolean onlyTargetIsSelfWithLinkedDevice = legacyTargets.isEmpty() && senderKeyTargets.isEmpty() && TextSecurePreferences.isMultiDevice(context);
    if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) {
        if (legacyTargets.size() > 0) {
            Log.i(TAG, "Need to do " + legacyTargets.size() + " legacy sends.");
        } else {
            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;
        final MessageSendLogDatabase messageLogDatabase = SignalDatabase.messageLog();
        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 sent using 1:1 to " + successCount + "/" + targets.size() + " legacy targets.");
    } else if (relatedMessageId != null) {
        SignalLocalMetrics.GroupMessageSend.onLegacyMessageSent(relatedMessageId.getId());
        SignalLocalMetrics.GroupMessageSend.onLegacySyncFinished(relatedMessageId.getId());
    }
    if (unregisteredTargets.size() > 0) {
        Log.w(TAG, "There are " + unregisteredTargets.size() + " unregistered targets. Including failure results.");
        List<SendMessageResult> unregisteredResults = unregisteredTargets.stream().filter(Recipient::hasServiceId).map(t -> SendMessageResult.unregisteredFailure(new SignalServiceAddress(t.requireServiceId(), t.getE164().orNull()))).collect(Collectors.toList());
        if (unregisteredResults.size() < unregisteredTargets.size()) {
            Log.w(TAG, "There are " + (unregisteredTargets.size() - unregisteredResults.size()) + " targets that have no UUID! Cannot report a failure for them.");
        }
        allResults.addAll(unregisteredResults);
    }
    return allResults;
}
Also used : SignalStore(org.thoughtcrime.securesms.keyvalue.SignalStore) SignalServiceDataMessage(org.whispersystems.signalservice.api.messages.SignalServiceDataMessage) SendMessageResult(org.whispersystems.signalservice.api.messages.SendMessageResult) NonNull(androidx.annotation.NonNull) RecipientUtil(org.thoughtcrime.securesms.recipients.RecipientUtil) SignalServiceAddress(org.whispersystems.signalservice.api.push.SignalServiceAddress) NotFoundException(org.whispersystems.signalservice.api.push.exceptions.NotFoundException) SenderKeyUtil(org.thoughtcrime.securesms.crypto.SenderKeyUtil) RecipientId(org.thoughtcrime.securesms.recipients.RecipientId) Map(java.util.Map) Recipient(org.thoughtcrime.securesms.recipients.Recipient) LegacyGroupEvents(org.whispersystems.signalservice.api.SignalServiceMessageSender.LegacyGroupEvents) PartialSendCompleteListener(org.whispersystems.signalservice.internal.push.http.PartialSendCompleteListener) ApplicationDependencies(org.thoughtcrime.securesms.dependencies.ApplicationDependencies) InvalidUnidentifiedAccessHeaderException(org.whispersystems.signalservice.internal.push.exceptions.InvalidUnidentifiedAccessHeaderException) Set(java.util.Set) UnidentifiedAccessUtil(org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) Log(org.signal.core.util.logging.Log) FeatureFlags(org.thoughtcrime.securesms.util.FeatureFlags) List(java.util.List) Nullable(androidx.annotation.Nullable) GroupId(org.thoughtcrime.securesms.groups.GroupId) NoSessionException(org.whispersystems.libsignal.NoSessionException) CancelationException(org.whispersystems.signalservice.api.CancelationException) Context(android.content.Context) RecipientAccessList(org.thoughtcrime.securesms.util.RecipientAccessList) SignalDatabase(org.thoughtcrime.securesms.database.SignalDatabase) ContentHint(org.whispersystems.signalservice.api.crypto.ContentHint) SignalServiceTypingMessage(org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage) SignalLocalMetrics(org.thoughtcrime.securesms.util.SignalLocalMetrics) WorkerThread(androidx.annotation.WorkerThread) GroupRecord(org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord) SenderKeyGroupEvents(org.whispersystems.signalservice.api.SignalServiceMessageSender.SenderKeyGroupEvents) CancelationSignal(org.whispersystems.signalservice.internal.push.http.CancelationSignal) HashMap(java.util.HashMap) InvalidRegistrationIdException(org.whispersystems.libsignal.InvalidRegistrationIdException) InvalidKeyException(org.whispersystems.libsignal.InvalidKeyException) UnidentifiedAccessPair(org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair) ArrayList(java.util.ArrayList) TextSecurePreferences(org.thoughtcrime.securesms.util.TextSecurePreferences) UnidentifiedAccess(org.whispersystems.signalservice.api.crypto.UnidentifiedAccess) SignalServiceCallMessage(org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage) MessageSendLogDatabase(org.thoughtcrime.securesms.database.MessageSendLogDatabase) LinkedList(java.util.LinkedList) DistributionId(org.whispersystems.signalservice.api.push.DistributionId) SignalServiceMessageSender(org.whispersystems.signalservice.api.SignalServiceMessageSender) MessageId(org.thoughtcrime.securesms.database.model.MessageId) Iterator(java.util.Iterator) IOException(java.io.IOException) Optional(org.whispersystems.libsignal.util.guava.Optional) TimeUnit(java.util.concurrent.TimeUnit) AtomicLong(java.util.concurrent.atomic.AtomicLong) UntrustedIdentityException(org.whispersystems.signalservice.api.crypto.UntrustedIdentityException) Collections(java.util.Collections) ArrayList(java.util.ArrayList) SignalServiceMessageSender(org.whispersystems.signalservice.api.SignalServiceMessageSender) NotFoundException(org.whispersystems.signalservice.api.push.exceptions.NotFoundException) MessageSendLogDatabase(org.thoughtcrime.securesms.database.MessageSendLogDatabase) GroupRecord(org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord) NoSessionException(org.whispersystems.libsignal.NoSessionException) CancelationException(org.whispersystems.signalservice.api.CancelationException) SignalServiceAddress(org.whispersystems.signalservice.api.push.SignalServiceAddress) Optional(org.whispersystems.libsignal.util.guava.Optional) InvalidRegistrationIdException(org.whispersystems.libsignal.InvalidRegistrationIdException) Recipient(org.thoughtcrime.securesms.recipients.Recipient) UnidentifiedAccessPair(org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair) DistributionId(org.whispersystems.signalservice.api.push.DistributionId) InvalidKeyException(org.whispersystems.libsignal.InvalidKeyException) LinkedList(java.util.LinkedList) SendMessageResult(org.whispersystems.signalservice.api.messages.SendMessageResult) ContentHint(org.whispersystems.signalservice.api.crypto.ContentHint) UnidentifiedAccess(org.whispersystems.signalservice.api.crypto.UnidentifiedAccess) AtomicLong(java.util.concurrent.atomic.AtomicLong) InvalidUnidentifiedAccessHeaderException(org.whispersystems.signalservice.internal.push.exceptions.InvalidUnidentifiedAccessHeaderException) WorkerThread(androidx.annotation.WorkerThread)

Aggregations

Context (android.content.Context)1 NonNull (androidx.annotation.NonNull)1 Nullable (androidx.annotation.Nullable)1 WorkerThread (androidx.annotation.WorkerThread)1 IOException (java.io.IOException)1 ArrayList (java.util.ArrayList)1 Collections (java.util.Collections)1 HashMap (java.util.HashMap)1 Iterator (java.util.Iterator)1 LinkedList (java.util.LinkedList)1 List (java.util.List)1 Map (java.util.Map)1 Objects (java.util.Objects)1 Set (java.util.Set)1 TimeUnit (java.util.concurrent.TimeUnit)1 AtomicLong (java.util.concurrent.atomic.AtomicLong)1 Collectors (java.util.stream.Collectors)1 Log (org.signal.core.util.logging.Log)1 SenderKeyUtil (org.thoughtcrime.securesms.crypto.SenderKeyUtil)1 UnidentifiedAccessUtil (org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil)1