Get shared backup tests working.

This commit is contained in:
Greyson Parrelli
2024-07-24 10:28:09 -04:00
committed by Nicholas Tinsley
parent 36640edfee
commit 7b0badef19
30 changed files with 184 additions and 108 deletions

View File

@@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeFe
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
import org.thoughtcrime.securesms.database.DistributionListTables
import org.thoughtcrime.securesms.database.KeyValueDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
@@ -249,19 +248,15 @@ object BackupRepository {
}
}
fun export(plaintext: Boolean = false, currentTime: Long = System.currentTimeMillis()): ByteArray {
/**
* Exports to a blob in memory. Should only be used for testing.
*/
fun debugExport(plaintext: Boolean = false, currentTime: Long = System.currentTimeMillis()): ByteArray {
val outputStream = ByteArrayOutputStream()
export(outputStream = outputStream, append = { mac -> outputStream.write(mac) }, plaintext = plaintext, currentTime = currentTime)
return outputStream.toByteArray()
}
fun validate(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData): ValidationResult {
val masterKey = SignalStore.svr.getOrCreateMasterKey()
val key = MessageBackupKey(masterKey.serialize(), Aci.parseFromBinary(selfData.aci.toByteArray()))
return MessageBackup.validate(key, MessageBackup.Purpose.REMOTE_BACKUP, inputStreamFactory, length)
}
/**
* @return The time the backup was created, or null if the backup could not be read.
*/
@@ -306,9 +301,6 @@ object BackupRepository {
SignalDatabase.recipients.setProfileKey(selfId, selfData.profileKey)
SignalDatabase.recipients.setProfileSharing(selfId, true)
// Add back my story after clearing data
DistributionListTables.insertInitialDistributionListAtCreationTime(it)
eventTimer.emit("setup")
val backupState = BackupState(backupKey)
val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(backupState)
@@ -373,6 +365,13 @@ object BackupRepository {
return ImportResult.Success(backupTime = header.backupTimeMs)
}
fun validate(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData): ValidationResult {
val masterKey = SignalStore.svr.getOrCreateMasterKey()
val key = MessageBackupKey(masterKey.serialize(), Aci.parseFromBinary(selfData.aci.toByteArray()))
return MessageBackup.validate(key, MessageBackup.Purpose.REMOTE_BACKUP, inputStreamFactory, length)
}
fun listRemoteMediaObjects(limit: Int, cursor: String? = null): NetworkResult<ArchiveGetMediaItemsResponse> {
val api = AppDependencies.signalServiceAccountManager.archiveApi
val backupKey = SignalStore.svr.getOrCreateMasterKey().deriveBackupKey()

View File

@@ -158,8 +158,8 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
MessageTypes.isChangeNumber(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.CHANGE_NUMBER)
}
MessageTypes.isBoostRequest(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.BOOST_REQUEST)
MessageTypes.isReleaseChannelDonationRequest(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.RELEASE_CHANNEL_DONATION_REQUEST)
}
MessageTypes.isEndSessionType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.END_SESSION)
@@ -176,6 +176,12 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
MessageTypes.isPaymentsRequestToActivate(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST)
}
MessageTypes.isUnsupportedMessageType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.UNSUPPORTED_PROTOCOL_MESSAGE)
}
MessageTypes.isReportedSpam(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.REPORTED_SPAM)
}
MessageTypes.isExpirationTimerUpdate(record.type) -> {
builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn.toInt()))
builder.expiresInMs = 0
@@ -265,7 +271,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
expiresInMs = if (record.expiresIn > 0) record.expiresIn else 0
revisions = emptyList()
sms = record.type.isSmsType()
if (MessageTypes.isCallLog(record.type)) {
if (record.type.isDirectionlessType()) {
directionless = ChatItem.DirectionlessMessageDetails()
} else if (MessageTypes.isOutgoingMessageType(record.type)) {
outgoing = ChatItem.OutgoingMessageDetails(
@@ -999,6 +1005,27 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
return MessageTypes.isOutgoingMessageType(this) || MessageTypes.isInboxType(this)
}
private fun Long.isDirectionlessType(): Boolean {
return MessageTypes.isCallLog(this) ||
MessageTypes.isExpirationTimerUpdate(this) ||
MessageTypes.isThreadMergeType(this) ||
MessageTypes.isSessionSwitchoverType(this) ||
MessageTypes.isProfileChange(this) ||
MessageTypes.isJoinedType(this) ||
MessageTypes.isIdentityUpdate(this) ||
MessageTypes.isIdentityVerified(this) ||
MessageTypes.isIdentityDefault(this) ||
MessageTypes.isReleaseChannelDonationRequest(this) ||
MessageTypes.isChangeNumber(this) ||
MessageTypes.isEndSessionType(this) ||
MessageTypes.isChatSessionRefresh(this) ||
MessageTypes.isBadDecryptType(this) ||
MessageTypes.isPaymentsActivated(this) ||
MessageTypes.isPaymentsRequestToActivate(this) ||
MessageTypes.isUnsupportedMessageType(this) ||
MessageTypes.isReportedSpam(this)
}
private fun String.e164ToLong(): Long? {
val fixed = if (this.startsWith("+")) {
this.substring(1)

View File

@@ -624,13 +624,14 @@ class ChatItemImportInserter(
SimpleChatUpdate.Type.IDENTITY_VERIFIED -> MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT or typeWithoutBase
SimpleChatUpdate.Type.IDENTITY_DEFAULT -> MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT or typeWithoutBase
SimpleChatUpdate.Type.CHANGE_NUMBER -> MessageTypes.CHANGE_NUMBER_TYPE
SimpleChatUpdate.Type.BOOST_REQUEST -> MessageTypes.BOOST_REQUEST_TYPE
SimpleChatUpdate.Type.RELEASE_CHANNEL_DONATION_REQUEST -> MessageTypes.RELEASE_CHANNEL_DONATION_REQUEST_TYPE
SimpleChatUpdate.Type.END_SESSION -> MessageTypes.END_SESSION_BIT or typeWithoutBase
SimpleChatUpdate.Type.CHAT_SESSION_REFRESH -> MessageTypes.ENCRYPTION_REMOTE_FAILED_BIT or typeWithoutBase
SimpleChatUpdate.Type.BAD_DECRYPT -> MessageTypes.BAD_DECRYPT_TYPE or typeWithoutBase
SimpleChatUpdate.Type.PAYMENTS_ACTIVATED -> MessageTypes.SPECIAL_TYPE_PAYMENTS_ACTIVATED or typeWithoutBase
SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST -> MessageTypes.SPECIAL_TYPE_PAYMENTS_ACTIVATE_REQUEST or typeWithoutBase
SimpleChatUpdate.Type.UNSUPPORTED_PROTOCOL_MESSAGE -> MessageTypes.UNSUPPORTED_MESSAGE_TYPE or typeWithoutBase
SimpleChatUpdate.Type.REPORTED_SPAM -> MessageTypes.SPECIAL_TYPE_REPORTED_SPAM or typeWithoutBase
}
}
updateMessage.expirationTimerChange != null -> {

View File

@@ -6,14 +6,15 @@
package org.thoughtcrime.securesms.backup.v2.database
import okio.ByteString.Companion.toByteString
import org.signal.core.util.CursorUtil
import org.signal.core.util.deleteAll
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireObject
import org.signal.core.util.select
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.backup.v2.BackupState
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
@@ -36,7 +37,6 @@ fun DistributionListTables.getAllForBackup(): List<BackupRecipient> {
val records = readableDatabase
.select()
.from(DistributionListTables.ListTable.TABLE_NAME)
.where(DistributionListTables.ListTable.IS_NOT_DELETED)
.run()
.readToList { cursor ->
val id: DistributionListId = DistributionListId.from(cursor.requireLong(DistributionListTables.ListTable.ID))
@@ -48,11 +48,11 @@ fun DistributionListTables.getAllForBackup(): List<BackupRecipient> {
id = id,
name = cursor.requireNonNullString(DistributionListTables.ListTable.NAME),
distributionId = DistributionId.from(cursor.requireNonNullString(DistributionListTables.ListTable.DISTRIBUTION_ID)),
allowsReplies = CursorUtil.requireBoolean(cursor, DistributionListTables.ListTable.ALLOWS_REPLIES),
allowsReplies = cursor.requireBoolean(DistributionListTables.ListTable.ALLOWS_REPLIES),
rawMembers = getRawMembers(id, privacyMode),
members = getMembers(id),
deletedAtTimestamp = 0L,
isUnknown = CursorUtil.requireBoolean(cursor, DistributionListTables.ListTable.IS_UNKNOWN),
members = getMembersForBackup(id),
deletedAtTimestamp = cursor.requireLong(DistributionListTables.ListTable.DELETION_TIMESTAMP),
isUnknown = cursor.requireBoolean(DistributionListTables.ListTable.IS_UNKNOWN),
privacyMode = privacyMode
)
)
@@ -82,7 +82,37 @@ fun DistributionListTables.getAllForBackup(): List<BackupRecipient> {
}
}
fun DistributionListTables.getMembersForBackup(id: DistributionListId): List<RecipientId> {
lateinit var privacyMode: DistributionListPrivacyMode
lateinit var rawMembers: List<RecipientId>
readableDatabase.withinTransaction {
privacyMode = getPrivacyMode(id)
rawMembers = getRawMembers(id, privacyMode)
}
return when (privacyMode) {
DistributionListPrivacyMode.ALL -> emptyList()
DistributionListPrivacyMode.ONLY_WITH -> rawMembers
DistributionListPrivacyMode.ALL_EXCEPT -> rawMembers
}
}
fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, backupState: BackupState): RecipientId? {
if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) {
val dlistId = createList(
name = "",
members = emptyList(),
distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)),
allowsReplies = false,
deletionTimestamp = dlistItem.deletionTimestamp,
storageId = null,
privacyMode = DistributionListPrivacyMode.ONLY_WITH
)!!
return SignalDatabase.distributionLists.getRecipientId(dlistId)!!
}
val dlist = dlistItem.distributionList ?: return null
val members: List<RecipientId> = dlist.memberRecipientIds
.mapNotNull { backupState.backupToLocalRecipientId[it] }
@@ -94,32 +124,22 @@ fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, ba
val distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId))
val privacyMode = dlist.privacyMode.toLocalPrivacyMode()
val dlistId = if (distributionId == DistributionId.MY_STORY) {
setPrivacyMode(DistributionListId.MY_STORY, privacyMode)
members.forEach { addMemberToList(DistributionListId.MY_STORY, privacyMode, it) }
setAllowsReplies(DistributionListId.MY_STORY, dlist.allowReplies)
DistributionListId.MY_STORY
} else {
createList(
name = dlist.name,
members = members,
distributionId = distributionId,
allowsReplies = dlist.allowReplies,
deletionTimestamp = dlistItem.deletionTimestamp ?: 0,
storageId = null,
privacyMode = privacyMode
)!!
}
val dlistId = createList(
name = dlist.name,
members = members,
distributionId = distributionId,
allowsReplies = dlist.allowReplies,
deletionTimestamp = dlistItem.deletionTimestamp ?: 0,
storageId = null,
privacyMode = privacyMode
)!!
return SignalDatabase.distributionLists.getRecipientId(dlistId)!!
}
fun DistributionListTables.clearAllDataForBackupRestore() {
writableDatabase
.deleteAll(DistributionListTables.ListTable.TABLE_NAME)
writableDatabase
.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME)
writableDatabase.deleteAll(DistributionListTables.ListTable.TABLE_NAME)
writableDatabase.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME)
}
private fun DistributionListPrivacyMode.toBackupPrivacyMode(): BackupDistributionList.PrivacyMode {

View File

@@ -45,6 +45,7 @@ fun ThreadTable.getThreadsForBackup(): ChatIterator {
${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}
FROM ${ThreadTable.TABLE_NAME}
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
WHERE ${ThreadTable.ACTIVE} = 1
"""
val cursor = readableDatabase.query(query)

View File

@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.push.UsernameLinkComponents
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.api.util.toByteArray
import java.util.Currency
object AccountDataProcessor {
@@ -48,6 +49,15 @@ object AccountDataProcessor {
familyName = selfRecord.signalProfileName.familyName,
avatarUrlPath = selfRecord.signalProfileAvatar ?: "",
username = selfRecord.username,
usernameLink = if (signalStore.accountValues.usernameLink != null) {
AccountData.UsernameLink(
entropy = signalStore.accountValues.usernameLink?.entropy?.toByteString() ?: EMPTY,
serverId = signalStore.accountValues.usernameLink?.serverId?.toByteArray()?.toByteString() ?: EMPTY,
color = signalStore.miscValues.usernameQrCodeColorScheme.toBackupUsernameColor() ?: AccountData.UsernameLink.Color.BLUE
)
} else {
null
},
accountSettings = AccountData.AccountSettings(
storyViewReceiptsEnabled = signalStore.storyValues.viewedReceiptsEnabled,
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context),
@@ -73,12 +83,6 @@ object AccountDataProcessor {
)
}
private fun InAppPaymentSubscriberRecord.toSubscriberData(manuallyCancelled: Boolean): AccountData.SubscriberData {
val subscriberId = subscriberId.bytes.toByteString()
val currencyCode = currency.currencyCode
return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled)
}
fun import(accountData: AccountData, selfId: RecipientId) {
SignalDatabase.recipients.restoreSelfFromBackup(accountData, selfId)
@@ -181,4 +185,23 @@ object AccountDataProcessor {
else -> UsernameQrCodeColorScheme.Blue
}
}
private fun UsernameQrCodeColorScheme.toBackupUsernameColor(): AccountData.UsernameLink.Color {
return when (this) {
UsernameQrCodeColorScheme.Blue -> AccountData.UsernameLink.Color.BLUE
UsernameQrCodeColorScheme.White -> AccountData.UsernameLink.Color.WHITE
UsernameQrCodeColorScheme.Grey -> AccountData.UsernameLink.Color.GREY
UsernameQrCodeColorScheme.Tan -> AccountData.UsernameLink.Color.OLIVE
UsernameQrCodeColorScheme.Green -> AccountData.UsernameLink.Color.GREEN
UsernameQrCodeColorScheme.Orange -> AccountData.UsernameLink.Color.ORANGE
UsernameQrCodeColorScheme.Pink -> AccountData.UsernameLink.Color.PINK
UsernameQrCodeColorScheme.Purple -> AccountData.UsernameLink.Color.PURPLE
}
}
private fun InAppPaymentSubscriberRecord.toSubscriberData(manuallyCancelled: Boolean): AccountData.SubscriberData {
val subscriberId = subscriberId.bytes.toByteString()
val currencyCode = currency.currencyCode
return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled)
}
}

View File

@@ -32,6 +32,7 @@ object RecipientBackupProcessor {
val selfId = db.recipientTable.getByAci(signalStore.accountValues.aci!!).get().toLong()
val releaseChannelId = signalStore.releaseChannelValues.releaseChannelRecipientId
if (releaseChannelId != null) {
state.recipientIds.add(releaseChannelId.toLong())
emitter.emit(
Frame(
recipient = BackupRecipient(