Simplify storing storage-service-specific recipient values.

This gives us the ability to separate things we need for the Recipient
class from things we only need for storage syncing.

Not only does this simplify the storage service model building code
(i.e. we no longer need to pass around a set of archived recipients),
but it also eliminates a join on the Identity table for building regular
recipients, which should help perf.
master
Greyson Parrelli 2020-10-06 10:24:14 -04:00
parent 38fa58c0a3
commit e00397620a
11 changed files with 181 additions and 143 deletions

View File

@ -15,7 +15,6 @@ import net.sqlcipher.database.SQLiteConstraintException;
import net.sqlcipher.database.SQLiteDatabase;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate;
import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.StringUtil;
@ -61,6 +62,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -128,7 +130,7 @@ public class RecipientDatabase extends Database {
private static final String IDENTITY_KEY = "identity_key";
private static final String[] RECIPIENT_PROJECTION = new String[] {
UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE,
ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE,
BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED,
PROFILE_KEY, PROFILE_KEY_CREDENTIAL,
SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI,
@ -144,20 +146,13 @@ public class RecipientDatabase extends Database {
private static final String[] ID_PROJECTION = new String[]{ID};
private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME};
public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME};
static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
.map(columnName -> TABLE_NAME + "." + columnName)
.toList().toArray(new String[0]);
private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
static final String[] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays.copyOfRange(TYPED_RECIPIENT_PROJECTION, 1, TYPED_RECIPIENT_PROJECTION.length);
private static final String[] RECIPIENT_FULL_PROJECTION = Stream.of(
new String[] { TABLE_NAME + "." + ID,
TABLE_NAME + "." + STORAGE_PROTO },
TYPED_RECIPIENT_PROJECTION,
new String[] {
IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS,
IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY
}).flatMap(Stream::of).toArray(String[]::new);
private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME};
public static final String[] CREATE_INDEXS = new String[] {
"CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");",
@ -595,11 +590,10 @@ public class RecipientDatabase extends Database {
public @NonNull RecipientSettings getRecipientSettings(@NonNull RecipientId id) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID;
String query = TABLE_NAME + "." + ID + " = ?";
String query = ID + " = ?";
String[] args = new String[] { id.serialize() };
try (Cursor cursor = database.query(table, RECIPIENT_FULL_PROJECTION, query, args, null, null, null)) {
try (Cursor cursor = database.query(TABLE_NAME, RECIPIENT_PROJECTION, query, args, null, null, null)) {
if (cursor != null && cursor.moveToNext()) {
return getRecipientSettings(context, cursor);
} else {
@ -814,12 +808,12 @@ public class RecipientDatabase extends Database {
Optional<IdentityRecord> newIdentityRecord = identityDatabase.getIdentity(recipientId);
if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED) &&
(!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED))
if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED) &&
(!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED))
{
IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), true, true);
} else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED) &&
(oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED))
} else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED) &&
(oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED))
{
IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), false, true);
}
@ -1054,12 +1048,20 @@ public class RecipientDatabase extends Database {
private List<RecipientSettings> getRecipientSettingsForSync(@Nullable String query, @Nullable String[] args) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID
+ " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID;
String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID
+ " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID
+ " LEFT OUTER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID;
List<RecipientSettings> out = new ArrayList<>();
String[] columns = Stream.of(RECIPIENT_FULL_PROJECTION,
new String[]{GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY }).flatMap(Stream::of).toArray(String[]::new);
String[] columns = Stream.of(TYPED_RECIPIENT_PROJECTION,
new String[]{ RecipientDatabase.TABLE_NAME + "." + STORAGE_PROTO,
GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY,
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ARCHIVED,
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.READ,
IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS,
IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY })
.flatMap(Stream::of)
.toArray(String[]::new);
try (Cursor cursor = db.query(table, columns, query, args, null, null, null)) {
while (cursor != null && cursor.moveToNext()) {
@ -1159,23 +1161,6 @@ public class RecipientDatabase extends Database {
int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY);
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull();
Optional<String> identityKeyRaw = CursorUtil.getString(cursor, IDENTITY_KEY);
Optional<Integer> identityStatusRaw = CursorUtil.getInt(cursor, IDENTITY_STATUS);
int masterKeyIndex = cursor.getColumnIndex(GroupDatabase.V2_MASTER_KEY);
GroupMasterKey groupMasterKey = null;
try {
if (masterKeyIndex != -1) {
byte[] blob = cursor.getBlob(masterKeyIndex);
if (blob != null) {
groupMasterKey = new GroupMasterKey(blob);
}
}
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
MaterialColor color;
byte[] profileKey = null;
@ -1206,30 +1191,57 @@ public class RecipientDatabase extends Database {
}
}
byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null;
byte[] identityKey = identityKeyRaw.transform(Base64::decodeOrThrow).orNull();
byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null;
byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null;
IdentityDatabase.VerifiedStatus identityStatus = identityStatusRaw.transform(IdentityDatabase.VerifiedStatus::forState).or(IdentityDatabase.VerifiedStatus.DEFAULT);
return new RecipientSettings(RecipientId.from(id), uuid, username, e164, email, groupId, groupMasterKey, GroupType.fromId(groupType), blocked, muteUntil,
return new RecipientSettings(RecipientId.from(id),
uuid,
username,
e164,
email,
groupId,
GroupType.fromId(groupType),
blocked,
muteUntil,
VibrateState.fromId(messageVibrateState),
VibrateState.fromId(callVibrateState),
Util.uri(messageRingtone), Util.uri(callRingtone),
color, defaultSubscriptionId, expireMessages,
Util.uri(messageRingtone),
Util.uri(callRingtone),
color,
defaultSubscriptionId,
expireMessages,
RegisteredState.fromId(registeredState),
profileKey, profileKeyCredential,
systemDisplayName, systemContactPhoto,
systemPhoneLabel, systemContactUri,
ProfileName.fromParts(profileGivenName, profileFamilyName), signalProfileAvatar,
AvatarHelper.hasAvatar(context, RecipientId.from(id)), profileSharing, lastProfileFetch,
notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
profileKey,
profileKeyCredential,
systemDisplayName,
systemContactPhoto,
systemPhoneLabel,
systemContactUri,
ProfileName.fromParts(profileGivenName, profileFamilyName),
signalProfileAvatar,
AvatarHelper.hasAvatar(context, RecipientId.from(id)),
profileSharing,
lastProfileFetch,
notificationChannel,
UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
forceSmsSelection,
Recipient.Capability.deserialize(uuidCapabilityValue),
Recipient.Capability.deserialize(groupsV2CapabilityValue),
InsightsBannerTier.fromId(insightsBannerTier),
storageKey, identityKey, identityStatus, MentionSetting.fromId(mentionSettingId),
storageProto);
storageKey,
MentionSetting.fromId(mentionSettingId),
getSyncExtras(cursor));
}
private static @NonNull RecipientSettings.SyncExtras getSyncExtras(@NonNull Cursor cursor) {
String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull();
byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null;
boolean archived = CursorUtil.getBoolean(cursor, ThreadDatabase.ARCHIVED).or(false);
GroupMasterKey groupMasterKey = CursorUtil.getBlob(cursor, GroupDatabase.V2_MASTER_KEY).transform(GroupUtil::requireMasterKey).orNull();
byte[] identityKey = CursorUtil.getString(cursor, IDENTITY_KEY).transform(Base64::decodeOrThrow).orNull();
VerifiedStatus identityStatus = CursorUtil.getInt(cursor, IDENTITY_STATUS).transform(VerifiedStatus::forState).or(VerifiedStatus.DEFAULT);
return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived);
}
public BulkOperationsHandle beginBulkSystemContactUpdate() {
@ -2537,7 +2549,6 @@ public class RecipientDatabase extends Database {
private final String e164;
private final String email;
private final GroupId groupId;
private final GroupMasterKey groupMasterKey;
private final GroupType groupType;
private final boolean blocked;
private final long muteUntil;
@ -2567,10 +2578,8 @@ public class RecipientDatabase extends Database {
private final Recipient.Capability groupsV2Capability;
private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId;
private final byte[] identityKey;
private final IdentityDatabase.VerifiedStatus identityStatus;
private final MentionSetting mentionSetting;
private final byte[] storageProto;
private final SyncExtras syncExtras;
RecipientSettings(@NonNull RecipientId id,
@Nullable UUID uuid,
@ -2578,7 +2587,6 @@ public class RecipientDatabase extends Database {
@Nullable String e164,
@Nullable String email,
@Nullable GroupId groupId,
@Nullable GroupMasterKey groupMasterKey,
@NonNull GroupType groupType,
boolean blocked,
long muteUntil,
@ -2608,10 +2616,8 @@ public class RecipientDatabase extends Database {
Recipient.Capability groupsV2Capability,
@NonNull InsightsBannerTier insightsBannerTier,
@Nullable byte[] storageId,
@Nullable byte[] identityKey,
@NonNull IdentityDatabase.VerifiedStatus identityStatus,
@NonNull MentionSetting mentionSetting,
@Nullable byte[] storageProto)
@NonNull SyncExtras syncExtras)
{
this.id = id;
this.uuid = uuid;
@ -2619,7 +2625,6 @@ public class RecipientDatabase extends Database {
this.e164 = e164;
this.email = email;
this.groupId = groupId;
this.groupMasterKey = groupMasterKey;
this.groupType = groupType;
this.blocked = blocked;
this.muteUntil = muteUntil;
@ -2649,10 +2654,8 @@ public class RecipientDatabase extends Database {
this.groupsV2Capability = groupsV2Capability;
this.insightsBannerTier = insightsBannerTier;
this.storageId = storageId;
this.identityKey = identityKey;
this.identityStatus = identityStatus;
this.mentionSetting = mentionSetting;
this.storageProto = storageProto;
this.syncExtras = syncExtras;
}
public RecipientId getId() {
@ -2679,13 +2682,6 @@ public class RecipientDatabase extends Database {
return groupId;
}
/**
* Only read populated for sync.
*/
public @Nullable GroupMasterKey getGroupMasterKey() {
return groupMasterKey;
}
public @NonNull GroupType getGroupType() {
return groupType;
}
@ -2802,20 +2798,57 @@ public class RecipientDatabase extends Database {
return storageId;
}
public @Nullable byte[] getIdentityKey() {
return identityKey;
}
public @NonNull IdentityDatabase.VerifiedStatus getIdentityStatus() {
return identityStatus;
}
public @NonNull MentionSetting getMentionSetting() {
return mentionSetting;
}
public @Nullable byte[] getStorageProto() {
return storageProto;
public @NonNull SyncExtras getSyncExtras() {
return syncExtras;
}
/**
* A bundle of data that's only necessary when syncing to storage service, not for a
* {@link Recipient}.
*/
public static class SyncExtras {
private final byte[] storageProto;
private final GroupMasterKey groupMasterKey;
private final byte[] identityKey;
private final VerifiedStatus identityStatus;
private final boolean archived;
public SyncExtras(@Nullable byte[] storageProto,
@Nullable GroupMasterKey groupMasterKey,
@Nullable byte[] identityKey,
@NonNull VerifiedStatus identityStatus,
boolean archived)
{
this.storageProto = storageProto;
this.groupMasterKey = groupMasterKey;
this.identityKey = identityKey;
this.identityStatus = identityStatus;
this.archived = archived;
}
public @Nullable byte[] getStorageProto() {
return storageProto;
}
public @Nullable GroupMasterKey getGroupMasterKey() {
return groupMasterKey;
}
public boolean isArchived() {
return archived;
}
public @Nullable byte[] getIdentityKey() {
return identityKey;
}
public @NonNull VerifiedStatus getIdentityStatus() {
return identityStatus;
}
}
}

View File

@ -144,7 +144,7 @@ public class ThreadDatabase extends Database {
.toList();
private static final List<String> COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION = Stream.concat(Stream.concat(Stream.of(TYPED_THREAD_PROJECTION),
Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION)),
Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION_NO_ID)),
Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION))
.toList();

View File

@ -512,7 +512,7 @@ public final class PushProcessMessageJob extends BaseJob {
Intent intent = new Intent(context, WebRtcCallService.class);
Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender());
RemotePeer remotePeer = new RemotePeer(recipient.getId());
byte[] remoteIdentityKey = recipient.getIdentityKey();
byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
intent.setAction(WebRtcCallService.ACTION_RECEIVE_OFFER)
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())
@ -538,7 +538,7 @@ public final class PushProcessMessageJob extends BaseJob {
Intent intent = new Intent(context, WebRtcCallService.class);
Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender());
RemotePeer remotePeer = new RemotePeer(recipient.getId());
byte[] remoteIdentityKey = recipient.getIdentityKey();
byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull();
intent.setAction(WebRtcCallService.ACTION_RECEIVE_ANSWER)
.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId())

View File

@ -83,11 +83,10 @@ public class StorageForcePushJob extends BaseJob {
long newVersion = currentVersion + 1;
Map<RecipientId, StorageId> newContactStorageIds = generateContactStorageIds(oldContactStorageIds);
Set<RecipientId> archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
List<SignalStorageRecord> inserts = Stream.of(oldContactStorageIds.keySet())
.map(recipientDatabase::getRecipientSettingsForSync)
.withoutNulls()
.map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw(), archivedRecipients))
.map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw()))
.toList();
SignalStorageRecord accountRecord = StorageSyncHelper.buildAccountRecord(context, Recipient.self().fresh());

View File

@ -152,8 +152,7 @@ public class StorageSyncJob extends BaseJob {
if (!keyDifference.isEmpty()) {
Log.i(TAG, "[Remote Newer] There's a difference in keys. Local-only: " + keyDifference.getLocalOnlyKeys().size() + ", Remote-only: " + keyDifference.getRemoteOnlyKeys().size());
Set<RecipientId> archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
List<SignalStorageRecord> localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys(), archivedRecipients);
List<SignalStorageRecord> localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys());
List<SignalStorageRecord> remoteOnly = accountManager.readStorageRecords(storageServiceKey, keyDifference.getRemoteOnlyKeys());
MergeResult mergeResult = StorageSyncHelper.resolveConflict(remoteOnly, localOnly);
WriteOperationResult writeOperationResult = StorageSyncHelper.createWriteOperation(remoteManifest.get().getVersion(), allLocalStorageKeys, mergeResult);
@ -212,15 +211,13 @@ public class StorageSyncJob extends BaseJob {
List<RecipientSettings> pendingDeletions = recipientDatabase.getPendingRecipientSyncDeletions();
Optional<SignalAccountRecord> pendingAccountInsert = StorageSyncHelper.getPendingAccountSyncInsert(context, self);
Optional<SignalAccountRecord> pendingAccountUpdate = StorageSyncHelper.getPendingAccountSyncUpdate(context, self);
Set<RecipientId> archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients();
Optional<LocalWriteResult> localWriteResult = StorageSyncHelper.buildStorageUpdatesForLocal(localManifestVersion,
allLocalStorageKeys,
pendingUpdates,
pendingInsertions,
pendingDeletions,
pendingAccountUpdate,
pendingAccountInsert,
archivedRecipients);
pendingAccountInsert);
if (localWriteResult.isPresent()) {
Log.i(TAG, String.format(Locale.ENGLISH, "[Local Changes] Local changes present. %d updates, %d inserts, %d deletes, account update: %b, account insert: %b.", pendingUpdates.size(), pendingInsertions.size(), pendingDeletions.size(), pendingAccountUpdate.isPresent(), pendingAccountInsert.isPresent()));
@ -273,7 +270,7 @@ public class StorageSyncJob extends BaseJob {
DatabaseFactory.getStorageKeyDatabase(context).getAllKeys());
}
private static @NonNull List<SignalStorageRecord> buildLocalStorageRecords(@NonNull Context context, @NonNull List<StorageId> ids, @NonNull Set<RecipientId> archivedRecipients) {
private static @NonNull List<SignalStorageRecord> buildLocalStorageRecords(@NonNull Context context, @NonNull List<StorageId> ids) {
Recipient self = Recipient.self().fresh();
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context);
@ -287,10 +284,10 @@ public class StorageSyncJob extends BaseJob {
case ManifestRecord.Identifier.Type.GROUPV2_VALUE:
RecipientSettings settings = recipientDatabase.getByStorageId(id.getRaw());
if (settings != null) {
if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getGroupMasterKey() == null) {
if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getSyncExtras().getGroupMasterKey() == null) {
Log.w(TAG, "Missing master key on gv2 recipient");
} else {
records.add(StorageSyncModels.localToRemoteRecord(settings, archivedRecipients));
records.add(StorageSyncModels.localToRemoteRecord(settings));
}
} else {
Log.w(TAG, "Missing local recipient model! Type: " + id.getType());

View File

@ -102,8 +102,6 @@ public class Recipient {
private final Capability groupsV2Capability;
private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId;
private final byte[] identityKey;
private final VerifiedStatus identityStatus;
private final MentionSetting mentionSetting;
@ -317,8 +315,6 @@ public class Recipient {
this.uuidCapability = Capability.UNKNOWN;
this.groupsV2Capability = Capability.UNKNOWN;
this.storageId = null;
this.identityKey = null;
this.identityStatus = VerifiedStatus.DEFAULT;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
}
@ -361,8 +357,6 @@ public class Recipient {
this.uuidCapability = details.uuidCapability;
this.groupsV2Capability = details.groupsV2Capability;
this.storageId = details.storageId;
this.identityKey = details.identityKey;
this.identityStatus = details.identityStatus;
this.mentionSetting = details.mentionSetting;
}
@ -782,14 +776,6 @@ public class Recipient {
return storageId;
}
public @NonNull VerifiedStatus getIdentityVerifiedStatus() {
return identityStatus;
}
public @Nullable byte[] getIdentityKey() {
return identityKey;
}
public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() {
return unidentifiedAccessMode;
}

View File

@ -65,9 +65,7 @@ public class RecipientDetails {
final Recipient.Capability uuidCapability;
final Recipient.Capability groupsV2Capability;
final InsightsBannerTier insightsBannerTier;
final byte[] storageId;
final byte[] identityKey;
final VerifiedStatus identityStatus;
final byte[] storageId;
final MentionSetting mentionSetting;
public RecipientDetails(@Nullable String name,
@ -113,8 +111,6 @@ public class RecipientDetails {
this.groupsV2Capability = settings.getGroupsV2Capability();
this.insightsBannerTier = settings.getInsightsBannerTier();
this.storageId = settings.getStorageId();
this.identityKey = settings.getIdentityKey();
this.identityStatus = settings.getIdentityStatus();
this.mentionSetting = settings.getMentionSetting();
if (name == null) this.name = settings.getSystemDisplayName();
@ -162,8 +158,6 @@ public class RecipientDetails {
this.uuidCapability = Recipient.Capability.UNKNOWN;
this.groupsV2Capability = Recipient.Capability.UNKNOWN;
this.storageId = null;
this.identityKey = null;
this.identityStatus = VerifiedStatus.DEFAULT;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
}

View File

@ -83,8 +83,7 @@ public final class StorageSyncHelper {
@NonNull List<RecipientSettings> inserts,
@NonNull List<RecipientSettings> deletes,
@NonNull Optional<SignalAccountRecord> accountUpdate,
@NonNull Optional<SignalAccountRecord> accountInsert,
@NonNull Set<RecipientId> archivedRecipients)
@NonNull Optional<SignalAccountRecord> accountInsert)
{
int accountCount = Stream.of(currentLocalKeys)
.filter(id -> id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE)
@ -119,12 +118,12 @@ public final class StorageSyncHelper {
Map<RecipientId, byte[]> storageKeyUpdates = new HashMap<>();
for (RecipientSettings insert : inserts) {
if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getGroupMasterKey() == null) {
if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getSyncExtras().getGroupMasterKey() == null) {
Log.w(TAG, "Missing master key on gv2 recipient");
continue;
}
storageInserts.add(StorageSyncModels.localToRemoteRecord(insert, archivedRecipients));
storageInserts.add(StorageSyncModels.localToRemoteRecord(insert));
switch (insert.getGroupType()) {
case NONE:
@ -173,7 +172,7 @@ public final class StorageSyncHelper {
throw new AssertionError("Unsupported type!");
}
storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw(), archivedRecipients));
storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw()));
storageDeletes.add(ByteBuffer.wrap(oldId.getRaw()));
completeIds.remove(oldId);
completeIds.add(newId);
@ -413,7 +412,7 @@ public final class StorageSyncHelper {
RecipientSettings settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettingsForSync(self.getId());
SignalAccountRecord account = new SignalAccountRecord.Builder(self.getStorageServiceId())
.setUnknownFields(settings != null ? settings.getStorageProto() : null)
.setUnknownFields(settings != null ? settings.getSyncExtras().getStorageProto() : null)
.setProfileKey(self.getProfileKey())
.setGivenName(self.getProfileName().getGivenName())
.setFamilyName(self.getProfileName().getFamilyName())

View File

@ -20,42 +20,42 @@ public final class StorageSyncModels {
private StorageSyncModels() {}
public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull Set<RecipientId> archived) {
public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings) {
if (settings.getStorageId() == null) {
throw new AssertionError("Must have a storage key!");
}
return localToRemoteRecord(settings, settings.getStorageId(), archived);
return localToRemoteRecord(settings, settings.getStorageId());
}
public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId, @NonNull Set<RecipientId> archived) {
public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId) {
switch (settings.getGroupType()) {
case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId, archived));
case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId, archived));
case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, archived));
case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId));
case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId));
case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId));
default: throw new AssertionError("Unsupported type!");
}
}
private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set<RecipientId> archived) {
private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId) {
if (recipient.getUuid() == null && recipient.getE164() == null) {
throw new AssertionError("Must have either a UUID or a phone number!");
}
return new SignalContactRecord.Builder(rawStorageId, new SignalServiceAddress(recipient.getUuid(), recipient.getE164()))
.setUnknownFields(recipient.getStorageProto())
.setUnknownFields(recipient.getSyncExtras().getStorageProto())
.setProfileKey(recipient.getProfileKey())
.setGivenName(recipient.getProfileName().getGivenName())
.setFamilyName(recipient.getProfileName().getFamilyName())
.setBlocked(recipient.isBlocked())
.setProfileSharingEnabled(recipient.isProfileSharing() || recipient.getSystemContactUri() != null)
.setIdentityKey(recipient.getIdentityKey())
.setIdentityState(localToRemoteIdentityState(recipient.getIdentityStatus()))
.setArchived(archived.contains(recipient.getId()))
.setIdentityKey(recipient.getSyncExtras().getIdentityKey())
.setIdentityState(localToRemoteIdentityState(recipient.getSyncExtras().getIdentityStatus()))
.setArchived(recipient.getSyncExtras().isArchived())
.build();
}
private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set<RecipientId> archived) {
private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId) {
GroupId groupId = recipient.getGroupId();
if (groupId == null) {
@ -67,14 +67,14 @@ public final class StorageSyncModels {
}
return new SignalGroupV1Record.Builder(rawStorageId, groupId.getDecodedId())
.setUnknownFields(recipient.getStorageProto())
.setUnknownFields(recipient.getSyncExtras().getStorageProto())
.setBlocked(recipient.isBlocked())
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(archived.contains(recipient.getId()))
.setArchived(recipient.getSyncExtras().isArchived())
.build();
}
private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set<RecipientId> archived) {
private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId) {
GroupId groupId = recipient.getGroupId();
if (groupId == null) {
@ -85,17 +85,17 @@ public final class StorageSyncModels {
throw new AssertionError("Group is not V2");
}
GroupMasterKey groupMasterKey = recipient.getGroupMasterKey();
GroupMasterKey groupMasterKey = recipient.getSyncExtras().getGroupMasterKey();
if (groupMasterKey == null) {
throw new AssertionError("Group master key not on recipient record");
}
return new SignalGroupV2Record.Builder(rawStorageId, groupMasterKey)
.setUnknownFields(recipient.getStorageProto())
.setUnknownFields(recipient.getSyncExtras().getStorageProto())
.setBlocked(recipient.isBlocked())
.setProfileSharingEnabled(recipient.isProfileSharing())
.setArchived(archived.contains(recipient.getId()))
.setArchived(recipient.getSyncExtras().isArchived())
.build();
}

View File

@ -26,6 +26,10 @@ public final class CursorUtil {
return requireInt(cursor, column) != 0;
}
public static byte[] requireBlob(@NonNull Cursor cursor, @NonNull String column) {
return cursor.getBlob(cursor.getColumnIndexOrThrow(column));
}
public static Optional<String> getString(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) {
return Optional.absent();
@ -41,4 +45,20 @@ public final class CursorUtil {
return Optional.of(requireInt(cursor, column));
}
}
public static Optional<Boolean> getBoolean(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) {
return Optional.absent();
} else {
return Optional.of(requireBoolean(cursor, column));
}
}
public static Optional<byte[]> getBlob(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) {
return Optional.absent();
} else {
return Optional.fromNullable(requireBlob(cursor, column));
}
}
}

View File

@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
@ -66,6 +68,14 @@ public final class GroupUtil {
return Optional.absent();
}
public static @NonNull GroupMasterKey requireMasterKey(@NonNull byte[] masterKey) {
try {
return new GroupMasterKey(masterKey);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public static @NonNull GroupDescription getNonV2GroupDescription(@NonNull Context context, @Nullable String encodedGroup) {
if (encodedGroup == null) {
return new GroupDescription(context, null);