diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java index 1d4bc58249..a533d0458b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; +import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -102,6 +103,11 @@ public class SignalProtocolStoreImpl implements SignalServiceDataStore { return sessionStore.getSubDeviceSessions(number); } + @Override + public Set getAllAddressesWithActiveSessions(List addressNames) { + return sessionStore.getAllAddressesWithActiveSessions(addressNames); + } + @Override public void storeSession(SignalProtocolAddress axolotlAddress, SessionRecord record) { sessionStore.storeSession(axolotlAddress, record); @@ -181,4 +187,13 @@ public class SignalProtocolStoreImpl implements SignalServiceDataStore { public boolean isMultiDevice() { return TextSecurePreferences.isMultiDevice(context); } + + @Override + public Transaction beginTransaction() { + DatabaseFactory.getInstance(context).getRawDatabase().beginTransaction(); + return () -> { + DatabaseFactory.getInstance(context).getRawDatabase().setTransactionSuccessful(); + DatabaseFactory.getInstance(context).getRawDatabase().endTransaction(); + }; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index c2ff66e7f2..71f357ad5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -16,6 +17,8 @@ import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.signalservice.api.SignalServiceSessionStore; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class TextSecureSessionStore implements SignalServiceSessionStore { @@ -97,6 +100,17 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { } } + @Override + public Set getAllAddressesWithActiveSessions(List addressNames) { + synchronized (LOCK) { + List rows = DatabaseFactory.getSessionDatabase(context).getAllFor(addressNames); + return rows.stream() + .filter(row -> isActive(row.getRecord())) + .map(row -> new SignalProtocolAddress(row.getAddress(), row.getDeviceId())) + .collect(Collectors.toSet()); + } + } + @Override public void archiveSession(SignalProtocolAddress address) { synchronized (LOCK) { @@ -145,4 +159,10 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { } } } + + private static boolean isActive(@Nullable SessionRecord record) { + return record != null && + record.hasSenderChain() && + record.getSessionVersion() == CiphertextMessage.CURRENT_VERSION; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java index 2670f08b8e..b753b5303a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionDatabase.java @@ -119,7 +119,27 @@ public class SessionDatabase extends Database { try { results.add(new SessionRow(CursorUtil.requireString(cursor, ADDRESS), CursorUtil.requireInt(cursor, DEVICE), - new SessionRecord(cursor.getBlob(cursor.getColumnIndexOrThrow(RECORD))))); + new SessionRecord(CursorUtil.requireBlob(cursor, RECORD)))); + } catch (IOException e) { + Log.w(TAG, e); + } + } + } + + return results; + } + + public @NonNull List getAllFor(@NonNull List addressNames) { + SQLiteDatabase database = databaseHelper.getSignalReadableDatabase(); + SqlUtil.Query query = SqlUtil.buildCollectionQuery(ADDRESS, addressNames); + List results = new LinkedList<>(); + + try (Cursor cursor = database.query(TABLE_NAME, null, query.getWhere(), query.getWhereArgs(), null, null, null)) { + while (cursor.moveToNext()) { + try { + results.add(new SessionRow(CursorUtil.requireString(cursor, ADDRESS), + CursorUtil.requireInt(cursor, DEVICE), + new SessionRecord(CursorUtil.requireBlob(cursor, RECORD)))); } catch (IOException e) { Log.w(TAG, e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java index 7b5ecc108a..79c355d7d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/GroupSendUtil.java @@ -10,7 +10,6 @@ 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.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.MessageSendLogDatabase; import org.thoughtcrime.securesms.database.model.MessageId; @@ -50,10 +49,8 @@ import java.util.LinkedList; import java.util.List; 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 { @@ -228,14 +225,19 @@ public final class GroupSendUtil { throw new CancelationException(); } - if (legacyTargets.size() > 0 || TextSecurePreferences.isMultiDevice(context)) { - Log.i(TAG, "Need to do " + legacyTargets.size() + " legacy sends."); + 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 targets = legacyTargets.stream().map(r -> recipients.getAddress(r.getId())).collect(Collectors.toList()); List> access = legacyTargets.stream().map(r -> recipients.getAccessPair(r.getId())).collect(Collectors.toList()); boolean recipientUpdate = isRecipientUpdate || allResults.size() > 0; - final MessageSendLogDatabase messageLogDatabase = DatabaseFactory.getMessageLogDatabase(context); final AtomicLong entryId = new AtomicLong(-1); final boolean includeInMessageLog = sendOperation.shouldIncludeInMessageLog(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceDataStore.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceDataStore.java index 0f50416ed0..55983b667a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceDataStore.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceDataStore.java @@ -2,6 +2,8 @@ package org.whispersystems.signalservice.api; import org.whispersystems.libsignal.state.SignalProtocolStore; +import java.io.Closeable; + /** * And extension of the normal protocol store interface that has additional methods that are needed * in the service layer, but not the protocol layer. @@ -11,4 +13,13 @@ public interface SignalServiceDataStore extends SignalProtocolStore, SignalServi * @return True if the active account has linked devices, otherwise false. */ boolean isMultiDevice(); + + /** + * @return Begins a transaction to improve the performance of multiple storage operations happening in a row. + */ + Transaction beginTransaction(); + + interface Transaction extends Closeable { + void close(); + } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 61a673b082..d0779429cd 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -122,7 +122,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -132,7 +131,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** @@ -1667,7 +1665,6 @@ public class SignalServiceMessageSender { .distinct() .map(SignalServiceAddress::new) .collect(Collectors.toList()); - if (needsSenderKey.size() > 0) { Log.i(TAG, "[sendGroupMessage] Need to send the distribution message to " + needsSenderKey.size() + " addresses."); SenderKeyDistributionMessage message = getOrCreateNewGroupSession(distributionId); @@ -1767,23 +1764,22 @@ public class SignalServiceMessageSender { } private GroupTargetInfo buildGroupTargetInfo(List recipients) { - Set destinations = new LinkedHashSet<>(); + List addressNames = recipients.stream().map(SignalServiceAddress::getIdentifier).collect(Collectors.toList()); + Set destinations = store.getAllAddressesWithActiveSessions(addressNames); + Map> devicesByAddressName = new HashMap<>(); + + for (SignalProtocolAddress destination : destinations) { + List devices = devicesByAddressName.containsKey(destination.getName()) ? devicesByAddressName.get(destination.getName()) : new LinkedList<>(); + devices.add(destination.getDeviceId()); + devicesByAddressName.put(destination.getName(), devices); + } + Map> recipientDevices = new HashMap<>(); for (SignalServiceAddress recipient : recipients) { - List devices = recipientDevices.containsKey(recipient) ? recipientDevices.get(recipient) : new LinkedList<>(); - - destinations.add(new SignalProtocolAddress(recipient.getUuid().toString(), SignalServiceAddress.DEFAULT_DEVICE_ID)); - devices.add(SignalServiceAddress.DEFAULT_DEVICE_ID); - - for (int deviceId : store.getSubDeviceSessions(recipient.getIdentifier())) { - if (store.containsSession(new SignalProtocolAddress(recipient.getIdentifier(), deviceId))) { - destinations.add(new SignalProtocolAddress(recipient.getUuid().toString(), deviceId)); - devices.add(deviceId); - } + if (devicesByAddressName.containsKey(recipient.getIdentifier())) { + recipientDevices.put(recipient, devicesByAddressName.get(recipient.getIdentifier())); } - - recipientDevices.put(recipient, devices); } return new GroupTargetInfo(new ArrayList<>(destinations), recipientDevices); @@ -2057,8 +2053,7 @@ public class SignalServiceMessageSender { return content; } - public static interface EventListener { - public void onSecurityEvent(SignalServiceAddress address); + public interface EventListener { + void onSecurityEvent(SignalServiceAddress address); } - } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceSessionStore.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceSessionStore.java index 8b924f480f..46f570f54b 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceSessionStore.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceSessionStore.java @@ -3,10 +3,14 @@ package org.whispersystems.signalservice.api; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.state.SessionStore; +import java.util.List; +import java.util.Set; + /** * And extension of the normal protocol session store interface that has additional methods that are * needed in the service layer, but not the protocol layer. */ public interface SignalServiceSessionStore extends SessionStore { void archiveSession(SignalProtocolAddress address); + Set getAllAddressesWithActiveSessions(List addressNames); }