The rest of the storage service unwrapping.

This commit is contained in:
Greyson Parrelli
2024-11-13 10:35:02 -05:00
parent 8746f483c0
commit 7dd1fc09c0
33 changed files with 754 additions and 1692 deletions

View File

@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.storage.recipientServiceAddresses
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.UUID
@@ -552,7 +553,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
}
fun getRecipientIdForSyncRecord(record: SignalStoryDistributionListRecord): RecipientId? {
val uuid: UUID = requireNotNull(UuidUtil.parseOrNull(record.identifier)) { "Incoming record did not have a valid identifier." }
val uuid: UUID = requireNotNull(UuidUtil.parseOrNull(record.proto.identifier)) { "Incoming record did not have a valid identifier." }
val distributionId = DistributionId.from(uuid)
return readableDatabase.query(
@@ -591,30 +592,30 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
}
fun applyStorageSyncStoryDistributionListInsert(insert: SignalStoryDistributionListRecord) {
val distributionId = DistributionId.from(UuidUtil.parseOrThrow(insert.identifier))
val distributionId = DistributionId.from(UuidUtil.parseOrThrow(insert.proto.identifier))
if (distributionId == DistributionId.MY_STORY) {
throw AssertionError("Should never try to insert My Story")
}
val privacyMode: DistributionListPrivacyMode = when {
insert.isBlockList && insert.recipients.isEmpty() -> DistributionListPrivacyMode.ALL
insert.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT
insert.proto.isBlockList && insert.proto.recipientServiceIds.isEmpty() -> DistributionListPrivacyMode.ALL
insert.proto.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT
else -> DistributionListPrivacyMode.ONLY_WITH
}
createList(
name = insert.name,
members = insert.recipients.map(RecipientId::from),
name = insert.proto.name,
members = insert.proto.recipientServiceAddresses.map(RecipientId::from),
distributionId = distributionId,
allowsReplies = insert.allowsReplies(),
deletionTimestamp = insert.deletedAtTimestamp,
allowsReplies = insert.proto.allowsReplies,
deletionTimestamp = insert.proto.deletedAtTimestamp,
privacyMode = privacyMode,
storageId = insert.id.raw
)
}
fun applyStorageSyncStoryDistributionListUpdate(update: StorageRecordUpdate<SignalStoryDistributionListRecord>) {
val distributionId = DistributionId.from(UuidUtil.parseOrThrow(update.new.identifier))
val distributionId = DistributionId.from(UuidUtil.parseOrThrow(update.new.proto.identifier))
val distributionListId: DistributionListId? = readableDatabase.query(ListTable.TABLE_NAME, arrayOf(ListTable.ID), "${ListTable.DISTRIBUTION_ID} = ?", SqlUtil.buildArgs(distributionId.toString()), null, null, null).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) {
@@ -632,26 +633,26 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
val recipientId = getRecipientId(distributionListId)!!
SignalDatabase.recipients.updateStorageId(recipientId, update.new.id.raw)
if (update.new.deletedAtTimestamp > 0L) {
if (update.new.proto.deletedAtTimestamp > 0L) {
if (distributionId == DistributionId.MY_STORY) {
Log.w(TAG, "Refusing to delete My Story.")
return
}
deleteList(distributionListId, update.new.deletedAtTimestamp)
deleteList(distributionListId, update.new.proto.deletedAtTimestamp)
return
}
val privacyMode: DistributionListPrivacyMode = when {
update.new.isBlockList && update.new.recipients.isEmpty() -> DistributionListPrivacyMode.ALL
update.new.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT
update.new.proto.isBlockList && update.new.proto.recipientServiceIds.isEmpty() -> DistributionListPrivacyMode.ALL
update.new.proto.isBlockList -> DistributionListPrivacyMode.ALL_EXCEPT
else -> DistributionListPrivacyMode.ONLY_WITH
}
writableDatabase.withinTransaction {
val listTableValues = contentValuesOf(
ListTable.ALLOWS_REPLIES to update.new.allowsReplies(),
ListTable.NAME to update.new.name,
ListTable.ALLOWS_REPLIES to update.new.proto.allowsReplies,
ListTable.NAME to update.new.proto.name,
ListTable.IS_UNKNOWN to false,
ListTable.PRIVACY_MODE to privacyMode.serialize()
)
@@ -664,7 +665,7 @@ class DistributionListTables constructor(context: Context?, databaseHelper: Sign
)
val currentlyInDistributionList = getRawMembers(distributionListId, privacyMode).toSet()
val shouldBeInDistributionList = update.new.recipients.map(RecipientId::from).toSet()
val shouldBeInDistributionList = update.new.proto.recipientServiceAddresses.map(RecipientId::from).toSet()
val toRemove = currentlyInDistributionList - shouldBeInDistributionList
val toAdd = shouldBeInDistributionList - currentlyInDistributionList

View File

@@ -24,7 +24,6 @@ import org.signal.core.util.nullIfBlank
import org.signal.core.util.nullIfEmpty
import org.signal.core.util.optionalString
import org.signal.core.util.or
import org.signal.core.util.orNull
import org.signal.core.util.readToList
import org.signal.core.util.readToSet
import org.signal.core.util.readToSingleBoolean
@@ -43,6 +42,7 @@ import org.signal.core.util.updateAll
import org.signal.core.util.withinTransaction
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.signal.storageservice.protos.groups.local.DecryptedGroup
@@ -113,12 +113,13 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.signalAci
import org.whispersystems.signalservice.api.storage.signalPni
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record
import java.io.Closeable
import java.io.IOException
import java.util.Collections
import java.util.LinkedList
import java.util.Objects
import java.util.Optional
import java.util.concurrent.TimeUnit
import kotlin.jvm.optionals.getOrNull
@@ -861,7 +862,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val recipientId: RecipientId
if (id < 0) {
Log.w(TAG, "[applyStorageSyncContactInsert] Failed to insert. Possibly merging.")
recipientId = getAndPossiblyMerge(aci = insert.aci.orNull(), pni = insert.pni.orNull(), e164 = insert.number.orNull(), pniVerified = insert.isPniSignatureVerified)
recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(insert.proto.aci), pni = PNI.parseOrNull(insert.proto.pni), e164 = insert.proto.e164.nullIfBlank(), pniVerified = insert.proto.pniSignatureVerified)
resolvePotentialUsernameConflicts(values.getAsString(USERNAME), recipientId)
db.update(TABLE_NAME, values, ID_WHERE, SqlUtil.buildArgs(recipientId))
@@ -869,18 +870,18 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
recipientId = RecipientId.from(id)
}
if (insert.identityKey.isPresent && (insert.aci.isPresent || insert.pni.isPresent)) {
if (insert.proto.identityKey.isNotEmpty() && (insert.proto.signalAci != null || insert.proto.signalPni != null)) {
try {
val serviceId: ServiceId = insert.aci.orNull() ?: insert.pni.get()
val identityKey = IdentityKey(insert.identityKey.get(), 0)
identities.updateIdentityAfterSync(serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.identityState))
val serviceId: ServiceId = insert.proto.signalAci ?: insert.proto.signalPni!!
val identityKey = IdentityKey(insert.proto.identityKey.toByteArray(), 0)
identities.updateIdentityAfterSync(serviceId.toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(insert.proto.identityState))
} catch (e: InvalidKeyException) {
Log.w(TAG, "Failed to process identity key during insert! Skipping.", e)
}
}
updateExtras(recipientId) {
it.hideStory(insert.shouldHideStory())
it.hideStory(insert.proto.hideStory)
}
threadDatabase.applyStorageSyncUpdate(recipientId, insert)
@@ -901,7 +902,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
var recipientId = getByColumn(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.old.id.raw)).get()
Log.w(TAG, "[applyStorageSyncContactUpdate] Found user $recipientId. Possibly merging.")
recipientId = getAndPossiblyMerge(aci = update.new.aci.orElse(null), pni = update.new.pni.orElse(null), e164 = update.new.number.orElse(null), pniVerified = update.new.isPniSignatureVerified)
recipientId = getAndPossiblyMerge(aci = ACI.parseOrNull(update.new.proto.aci), pni = PNI.parseOrNull(update.new.proto.pni), e164 = update.new.proto.e164.nullIfBlank(), pniVerified = update.new.proto.pniSignatureVerified)
Log.w(TAG, "[applyStorageSyncContactUpdate] Merged into $recipientId")
resolvePotentialUsernameConflicts(values.getAsString(USERNAME), recipientId)
@@ -919,9 +920,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
try {
val oldIdentityRecord = identityStore.getIdentityRecord(recipientId)
if (update.new.identityKey.isPresent && update.new.aci.isPresent) {
val identityKey = IdentityKey(update.new.identityKey.get(), 0)
identities.updateIdentityAfterSync(update.new.aci.get().toString(), recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.identityState))
if (update.new.proto.identityKey.isNotEmpty() && update.new.proto.signalAci != null) {
val identityKey = IdentityKey(update.new.proto.identityKey.toByteArray(), 0)
identities.updateIdentityAfterSync(update.new.proto.aci, recipientId, identityKey, StorageSyncModels.remoteToLocalIdentityStatus(update.new.proto.identityState))
}
val newIdentityRecord = identityStore.getIdentityRecord(recipientId)
@@ -935,7 +936,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
updateExtras(recipientId) {
it.hideStory(update.new.shouldHideStory())
it.hideStory(update.new.proto.hideStory)
}
threads.applyStorageSyncUpdate(recipientId, update.new)
@@ -968,13 +969,13 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
throw AssertionError("Had an update, but it didn't match any rows!")
}
val recipient = Recipient.externalGroupExact(GroupId.v1orThrow(update.old.groupId))
val recipient = Recipient.externalGroupExact(GroupId.v1orThrow(update.old.proto.id.toByteArray()))
threads.applyStorageSyncUpdate(recipient.id, update.new)
recipient.live().refresh()
}
fun applyStorageSyncGroupV2Insert(insert: SignalGroupV2Record) {
val masterKey = insert.masterKeyOrThrow
val masterKey = GroupMasterKey(insert.proto.masterKey.toByteArray())
val groupId = GroupId.v2(masterKey)
val values = getValuesForStorageGroupV2(insert, true)
@@ -991,12 +992,12 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
Log.w(TAG, "Unable to create restore placeholder for $groupId, group already exists")
}
groups.setShowAsStoryState(groupId, insert.storySendMode.toShowAsStoryState())
groups.setShowAsStoryState(groupId, insert.proto.storySendMode.toShowAsStoryState())
val recipient = Recipient.externalGroupExact(groupId)
updateExtras(recipient.id) {
it.hideStory(insert.shouldHideStory())
it.hideStory(insert.proto.hideStory)
}
Log.i(TAG, "Scheduling request for latest group info for $groupId")
@@ -1013,15 +1014,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
throw AssertionError("Had an update, but it didn't match any rows!")
}
val masterKey = update.old.masterKeyOrThrow
val masterKey = GroupMasterKey(update.old.proto.masterKey.toByteArray())
val groupId = GroupId.v2(masterKey)
val recipient = Recipient.externalGroupExact(groupId)
updateExtras(recipient.id) {
it.hideStory(update.new.shouldHideStory())
it.hideStory(update.new.proto.hideStory)
}
groups.setShowAsStoryState(groupId, update.new.storySendMode.toShowAsStoryState())
groups.setShowAsStoryState(groupId, update.new.proto.storySendMode.toShowAsStoryState())
threads.applyStorageSyncUpdate(recipient.id, update.new)
recipient.live().refresh()
}
@@ -1051,7 +1052,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.new.id.raw))
if (update.new.proto.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(update.new.serializeUnknownFields()!!))
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(update.new.serializedUnknowns!!))
} else {
putNull(STORAGE_SERVICE_PROTO)
}
@@ -4160,68 +4161,68 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
private fun getValuesForStorageContact(contact: SignalContactRecord, isInsert: Boolean): ContentValues {
return ContentValues().apply {
val profileName = ProfileName.fromParts(contact.profileGivenName.orElse(null), contact.profileFamilyName.orElse(null))
val systemName = ProfileName.fromParts(contact.systemGivenName.orElse(null), contact.systemFamilyName.orElse(null))
val username = contact.username.orElse(null)
val nickname = ProfileName.fromParts(contact.nicknameGivenName.orNull(), contact.nicknameFamilyName.orNull())
val profileName = ProfileName.fromParts(contact.proto.givenName.nullIfBlank(), contact.proto.familyName.nullIfBlank())
val systemName = ProfileName.fromParts(contact.proto.systemGivenName.nullIfBlank(), contact.proto.systemFamilyName.nullIfBlank())
val username = contact.proto.username.nullIfBlank()
val nickname = ProfileName.fromParts(contact.proto.nickname?.given, contact.proto.nickname?.family)
put(ACI_COLUMN, contact.aci.orElse(null)?.toString())
put(PNI_COLUMN, contact.pni.orElse(null)?.toString())
put(E164, contact.number.orElse(null))
put(ACI_COLUMN, contact.proto.signalAci?.toString())
put(PNI_COLUMN, contact.proto.signalPni?.toString())
put(E164, contact.proto.e164.nullIfBlank())
put(PROFILE_GIVEN_NAME, profileName.givenName)
put(PROFILE_FAMILY_NAME, profileName.familyName)
put(PROFILE_JOINED_NAME, profileName.toString())
put(SYSTEM_GIVEN_NAME, systemName.givenName)
put(SYSTEM_FAMILY_NAME, systemName.familyName)
put(SYSTEM_JOINED_NAME, systemName.toString())
put(SYSTEM_NICKNAME, contact.systemNickname.orElse(null))
put(PROFILE_KEY, contact.profileKey.map { source -> Base64.encodeWithPadding(source) }.orElse(null))
put(SYSTEM_NICKNAME, contact.proto.systemNickname.nullIfBlank())
put(PROFILE_KEY, contact.proto.profileKey.takeIf { it.isNotEmpty() }?.let { source -> Base64.encodeWithPadding(source.toByteArray()) })
put(USERNAME, if (TextUtils.isEmpty(username)) null else username)
put(PROFILE_SHARING, if (contact.isProfileSharingEnabled) "1" else "0")
put(BLOCKED, if (contact.isBlocked) "1" else "0")
put(MUTE_UNTIL, contact.muteUntil)
put(PROFILE_SHARING, contact.proto.whitelisted.toInt())
put(BLOCKED, contact.proto.blocked.toInt())
put(MUTE_UNTIL, contact.proto.mutedUntilTimestamp)
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(contact.id.raw))
put(HIDDEN, contact.isHidden)
put(PNI_SIGNATURE_VERIFIED, contact.isPniSignatureVerified.toInt())
put(HIDDEN, contact.proto.hidden)
put(PNI_SIGNATURE_VERIFIED, contact.proto.pniSignatureVerified.toInt())
put(NICKNAME_GIVEN_NAME, nickname.givenName.nullIfBlank())
put(NICKNAME_FAMILY_NAME, nickname.familyName.nullIfBlank())
put(NICKNAME_JOINED_NAME, nickname.toString().nullIfBlank())
put(NOTE, contact.note.orNull().nullIfBlank())
put(NOTE, contact.proto.note.nullIfBlank())
if (contact.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(Objects.requireNonNull(contact.serializeUnknownFields())))
if (contact.proto.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(contact.serializedUnknowns!!))
} else {
putNull(STORAGE_SERVICE_PROTO)
}
put(UNREGISTERED_TIMESTAMP, contact.unregisteredTimestamp)
if (contact.unregisteredTimestamp > 0L) {
put(UNREGISTERED_TIMESTAMP, contact.proto.unregisteredAtTimestamp)
if (contact.proto.unregisteredAtTimestamp > 0L) {
put(REGISTERED, RegisteredState.NOT_REGISTERED.id)
} else if (contact.aci.isPresent) {
} else if (contact.proto.signalAci != null) {
put(REGISTERED, RegisteredState.REGISTERED.id)
} else {
Log.w(TAG, "Contact is marked as registered, but has no serviceId! Can't locally mark registered. (Phone: ${contact.number.orElse("null")}, Username: ${username?.isNotEmpty()})")
Log.w(TAG, "Contact is marked as registered, but has no serviceId! Can't locally mark registered. (Phone: ${contact.proto.e164.nullIfBlank()}, Username: ${username?.isNotEmpty()})")
}
if (isInsert) {
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.aci.map { it.toString() }.or(contact.pni.map { it.toString() }).orNull(), contact.number.orNull()).serialize())
put(AVATAR_COLOR, AvatarColorHash.forAddress(contact.proto.signalAci?.toString() ?: contact.proto.signalPni?.toString(), contact.proto.e164).serialize())
}
}
}
private fun getValuesForStorageGroupV1(groupV1: SignalGroupV1Record, isInsert: Boolean): ContentValues {
return ContentValues().apply {
val groupId = GroupId.v1orThrow(groupV1.groupId)
val groupId = GroupId.v1orThrow(groupV1.proto.id.toByteArray())
put(GROUP_ID, groupId.toString())
put(TYPE, RecipientType.GV1.id)
put(PROFILE_SHARING, if (groupV1.isProfileSharingEnabled) "1" else "0")
put(BLOCKED, if (groupV1.isBlocked) "1" else "0")
put(MUTE_UNTIL, groupV1.muteUntil)
put(PROFILE_SHARING, if (groupV1.proto.whitelisted) "1" else "0")
put(BLOCKED, if (groupV1.proto.blocked) "1" else "0")
put(MUTE_UNTIL, groupV1.proto.mutedUntilTimestamp)
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(groupV1.id.raw))
if (groupV1.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(groupV1.serializeUnknownFields()))
if (groupV1.proto.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(groupV1.serializedUnknowns!!))
} else {
putNull(STORAGE_SERVICE_PROTO)
}
@@ -4234,18 +4235,18 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
private fun getValuesForStorageGroupV2(groupV2: SignalGroupV2Record, isInsert: Boolean): ContentValues {
return ContentValues().apply {
val groupId = GroupId.v2(groupV2.masterKeyOrThrow)
val groupId = GroupId.v2(GroupMasterKey(groupV2.proto.masterKey.toByteArray()))
put(GROUP_ID, groupId.toString())
put(TYPE, RecipientType.GV2.id)
put(PROFILE_SHARING, if (groupV2.isProfileSharingEnabled) "1" else "0")
put(BLOCKED, if (groupV2.isBlocked) "1" else "0")
put(MUTE_UNTIL, groupV2.muteUntil)
put(PROFILE_SHARING, if (groupV2.proto.whitelisted) "1" else "0")
put(BLOCKED, if (groupV2.proto.blocked) "1" else "0")
put(MUTE_UNTIL, groupV2.proto.mutedUntilTimestamp)
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(groupV2.id.raw))
put(MENTION_SETTING, if (groupV2.notifyForMentionsWhenMuted()) MentionSetting.ALWAYS_NOTIFY.id else MentionSetting.DO_NOT_NOTIFY.id)
put(MENTION_SETTING, if (groupV2.proto.dontNotifyForMentionsIfMuted) MentionSetting.DO_NOT_NOTIFY.id else MentionSetting.ALWAYS_NOTIFY.id)
if (groupV2.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(groupV2.serializeUnknownFields()))
if (groupV2.proto.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(groupV2.serializedUnknowns!!))
} else {
putNull(STORAGE_SERVICE_PROTO)
}

View File

@@ -1510,15 +1510,15 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
}
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalContactRecord) {
applyStorageSyncUpdate(recipientId, record.isArchived, record.isForcedUnread)
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
}
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalGroupV1Record) {
applyStorageSyncUpdate(recipientId, record.isArchived, record.isForcedUnread)
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
}
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalGroupV2Record) {
applyStorageSyncUpdate(recipientId, record.isArchived, record.isForcedUnread)
applyStorageSyncUpdate(recipientId, record.proto.archived, record.proto.markedUnread)
}
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalAccountRecord) {