Update to the latest backup v2 spec.
Removes some dead protos, removes some sticker details, adds in gift badges.
This commit is contained in:
parent
3f1cb65e02
commit
6fa8337058
5 changed files with 153 additions and 65 deletions
|
@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
|
|||
import org.thoughtcrime.securesms.backup.v2.proto.ExpirationTimerChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Group
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
|
||||
|
@ -50,6 +51,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.ThreadMergeChatUpdate
|
|||
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
|
@ -1236,6 +1238,76 @@ class ImportExportTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun giftBadgeMessage() {
|
||||
var dateSentStart = 100L
|
||||
importExport(
|
||||
*standardFrames,
|
||||
alice,
|
||||
buildChat(alice, 1),
|
||||
ChatItem(
|
||||
chatId = 1,
|
||||
authorId = alice.id,
|
||||
dateSent = dateSentStart++,
|
||||
incoming = ChatItem.IncomingMessageDetails(
|
||||
dateReceived = dateSentStart,
|
||||
dateServerSent = dateSentStart,
|
||||
read = true,
|
||||
sealedSender = true
|
||||
),
|
||||
giftBadge = GiftBadge(
|
||||
receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
|
||||
state = GiftBadge.State.OPENED
|
||||
)
|
||||
),
|
||||
ChatItem(
|
||||
chatId = 1,
|
||||
authorId = alice.id,
|
||||
dateSent = dateSentStart++,
|
||||
incoming = ChatItem.IncomingMessageDetails(
|
||||
dateReceived = dateSentStart,
|
||||
dateServerSent = dateSentStart,
|
||||
read = true,
|
||||
sealedSender = true
|
||||
),
|
||||
giftBadge = GiftBadge(
|
||||
receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
|
||||
state = GiftBadge.State.FAILED
|
||||
)
|
||||
),
|
||||
ChatItem(
|
||||
chatId = 1,
|
||||
authorId = alice.id,
|
||||
dateSent = dateSentStart++,
|
||||
incoming = ChatItem.IncomingMessageDetails(
|
||||
dateReceived = dateSentStart,
|
||||
dateServerSent = dateSentStart,
|
||||
read = true,
|
||||
sealedSender = true
|
||||
),
|
||||
giftBadge = GiftBadge(
|
||||
receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
|
||||
state = GiftBadge.State.REDEEMED
|
||||
)
|
||||
),
|
||||
ChatItem(
|
||||
chatId = 1,
|
||||
authorId = alice.id,
|
||||
dateSent = dateSentStart++,
|
||||
incoming = ChatItem.IncomingMessageDetails(
|
||||
dateReceived = dateSentStart,
|
||||
dateServerSent = dateSentStart,
|
||||
read = true,
|
||||
sealedSender = true
|
||||
),
|
||||
giftBadge = GiftBadge(
|
||||
receiptCredentialPresentation = Util.getSecretBytes(32).toByteString(),
|
||||
state = GiftBadge.State.UNOPENED
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun enumerateIncomingMessageDetails(dateSent: Long): List<ChatItem.IncomingMessageDetails> {
|
||||
val details = mutableListOf<ChatItem.IncomingMessageDetails>()
|
||||
details.add(
|
||||
|
@ -1508,17 +1580,29 @@ class ImportExportTest {
|
|||
|
||||
private inline fun <reified T : Any, R : Comparable<R>> prettyAssertEquals(import: List<T>, export: List<T>, crossinline selector: (T) -> R?) {
|
||||
if (import.size != export.size) {
|
||||
var msg = StringBuilder()
|
||||
val msg = StringBuilder()
|
||||
msg.append("There's a different number of items in the lists!\n\n")
|
||||
|
||||
msg.append("Imported:\n")
|
||||
for (i in import) {
|
||||
msg.append(i)
|
||||
msg.append("\n")
|
||||
}
|
||||
if (import.isEmpty()) {
|
||||
msg.append("<None>")
|
||||
}
|
||||
msg.append("\n")
|
||||
msg.append("Exported:\n")
|
||||
for (i in export) {
|
||||
msg.append(i)
|
||||
msg.append("\n")
|
||||
}
|
||||
if (export.isEmpty()) {
|
||||
msg.append("<None>")
|
||||
}
|
||||
Assert.fail(msg.toString())
|
||||
}
|
||||
|
||||
Assert.assertEquals(import.size, export.size)
|
||||
val sortedImport = import.sortedBy(selector)
|
||||
val sortedExport = export.sortedBy(selector)
|
||||
|
|
|
@ -8,8 +8,6 @@ package org.thoughtcrime.securesms.backup.v2.database
|
|||
import android.database.Cursor
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decode
|
||||
import org.signal.core.util.Base64.decodeOrThrow
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireBlob
|
||||
|
@ -59,6 +57,7 @@ import org.thoughtcrime.securesms.database.model.Mention
|
|||
import org.thoughtcrime.securesms.database.model.ReactionRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||
|
@ -79,6 +78,7 @@ import java.util.LinkedList
|
|||
import java.util.Queue
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge
|
||||
|
||||
/**
|
||||
* An iterator for chat items with a clever performance twist: rather than do the extra queries one at a time (for reactions,
|
||||
|
@ -173,7 +173,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
MessageTypes.isSessionSwitchoverType(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
sessionSwitchover = try {
|
||||
val event = SessionSwitchoverEvent.ADAPTER.decode(decodeOrThrow(record.body!!))
|
||||
val event = SessionSwitchoverEvent.ADAPTER.decode(Base64.decodeOrThrow(record.body!!))
|
||||
SessionSwitchoverChatUpdate(event.e164.e164ToLong()!!)
|
||||
} catch (e: Exception) {
|
||||
SessionSwitchoverChatUpdate()
|
||||
|
@ -183,7 +183,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
MessageTypes.isThreadMergeType(record.type) -> {
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
threadMerge = try {
|
||||
val event = ThreadMergeEvent.ADAPTER.decode(decodeOrThrow(record.body!!))
|
||||
val event = ThreadMergeEvent.ADAPTER.decode(Base64.decodeOrThrow(record.body!!))
|
||||
ThreadMergeChatUpdate(event.previousE164.e164ToLong()!!)
|
||||
} catch (e: Exception) {
|
||||
ThreadMergeChatUpdate()
|
||||
|
@ -198,7 +198,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
)
|
||||
} else if (record.body != null) {
|
||||
try {
|
||||
val decoded: ByteArray = decode(record.body)
|
||||
val decoded: ByteArray = Base64.decode(record.body)
|
||||
val context = DecryptedGroupV2Context.ADAPTER.decode(decoded)
|
||||
builder.updateMessage = ChatUpdateMessage(
|
||||
groupChange = GroupsV2UpdateMessageConverter.translateDecryptedChange(selfIds = SignalStore.account().getServiceIds(), context)
|
||||
|
@ -347,6 +347,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
)
|
||||
}
|
||||
}
|
||||
MessageTypes.isGiftBadge(record.type) -> { builder.giftBadge = record.toBackupGiftBadge() }
|
||||
else -> {
|
||||
if (record.body == null && !attachmentsById.containsKey(record.id)) {
|
||||
Log.w(TAG, "Record missing a body and doesnt have attachments, skipping")
|
||||
|
@ -479,6 +480,25 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
}
|
||||
}
|
||||
|
||||
private fun BackupMessageRecord.toBackupGiftBadge(): BackupGiftBadge {
|
||||
val giftBadge = try {
|
||||
GiftBadge.ADAPTER.decode(Base64.decode(this.body ?: ""))
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to decode GiftBadge!")
|
||||
return BackupGiftBadge()
|
||||
}
|
||||
|
||||
return BackupGiftBadge(
|
||||
receiptCredentialPresentation = giftBadge.redemptionToken,
|
||||
state = when (giftBadge.redemptionState) {
|
||||
GiftBadge.RedemptionState.REDEEMED -> BackupGiftBadge.State.REDEEMED
|
||||
GiftBadge.RedemptionState.FAILED -> BackupGiftBadge.State.FAILED
|
||||
GiftBadge.RedemptionState.PENDING -> BackupGiftBadge.State.UNOPENED
|
||||
GiftBadge.RedemptionState.STARTED -> BackupGiftBadge.State.OPENED
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<DatabaseAttachment>.toBackupQuoteAttachments(): List<Quote.QuotedAttachment> {
|
||||
return this.map { attachment ->
|
||||
Quote.QuotedAttachment(
|
||||
|
@ -507,7 +527,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
builder.backupLocator = FilePointer.BackupLocator(
|
||||
mediaName = archiveMediaName ?: this.getMediaName().toString(),
|
||||
cdnNumber = if (archiveMediaName != null) archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed
|
||||
key = decode(remoteKey).toByteString(),
|
||||
key = Base64.decode(remoteKey).toByteString(),
|
||||
size = this.size.toInt(),
|
||||
digest = remoteDigest.toByteString()
|
||||
)
|
||||
|
@ -519,7 +539,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
cdnKey = this.remoteLocation,
|
||||
cdnNumber = this.cdn.cdnNumber,
|
||||
uploadTimestamp = this.uploadTimestamp,
|
||||
key = decode(remoteKey).toByteString(),
|
||||
key = Base64.decode(remoteKey).toByteString(),
|
||||
size = this.size.toInt(),
|
||||
digest = remoteDigest.toByteString()
|
||||
)
|
||||
|
@ -538,7 +558,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
|||
} else {
|
||||
MessageAttachment.Flag.NONE
|
||||
},
|
||||
uuid = uuid?.let { UuidUtil.toByteString(uuid) }
|
||||
clientUuid = uuid?.let { UuidUtil.toByteString(uuid) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.database.model.Mention
|
|||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.CryptoValue
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
|
@ -77,6 +78,7 @@ import org.whispersystems.signalservice.internal.push.DataMessage
|
|||
import java.math.BigInteger
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge
|
||||
|
||||
/**
|
||||
* An object that will ingest all fo the [ChatItem]s you want to write, buffer them until hitting a specified batch size, and then batch insert them
|
||||
|
@ -410,6 +412,7 @@ class ChatItemImportInserter(
|
|||
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.REMOTE_DELETED, 1)
|
||||
this.updateMessage != null -> contentValues.addUpdateMessage(this.updateMessage)
|
||||
this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
|
||||
this.giftBadge != null -> contentValues.addGiftBadge(this.giftBadge)
|
||||
}
|
||||
|
||||
return contentValues
|
||||
|
@ -517,6 +520,10 @@ class ChatItemImportInserter(
|
|||
type = type or MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT
|
||||
}
|
||||
|
||||
if (this.giftBadge != null) {
|
||||
type = type or MessageTypes.SPECIAL_TYPE_GIFT_BADGE
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
|
@ -695,6 +702,20 @@ class ChatItemImportInserter(
|
|||
)
|
||||
}
|
||||
|
||||
private fun ContentValues.addGiftBadge(giftBadge: BackupGiftBadge) {
|
||||
val dbGiftBadge = GiftBadge(
|
||||
redemptionToken = giftBadge.receiptCredentialPresentation,
|
||||
redemptionState = when (giftBadge.state) {
|
||||
BackupGiftBadge.State.UNOPENED -> GiftBadge.RedemptionState.PENDING
|
||||
BackupGiftBadge.State.OPENED -> GiftBadge.RedemptionState.STARTED
|
||||
BackupGiftBadge.State.REDEEMED -> GiftBadge.RedemptionState.REDEEMED
|
||||
BackupGiftBadge.State.FAILED -> GiftBadge.RedemptionState.FAILED
|
||||
}
|
||||
)
|
||||
|
||||
put(MessageTable.BODY, Base64.encodeWithPadding(GiftBadge.ADAPTER.encode(dbGiftBadge)))
|
||||
}
|
||||
|
||||
private fun String?.tryParseMoney(): Money? {
|
||||
if (this.isNullOrEmpty()) {
|
||||
return null
|
||||
|
@ -915,7 +936,7 @@ class ChatItemImportInserter(
|
|||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
wasDownloaded = wasDownloaded,
|
||||
uuid = uuid
|
||||
uuid = clientUuid
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -927,7 +948,7 @@ class ChatItemImportInserter(
|
|||
wasDownloaded = wasDownloaded,
|
||||
contentType = contentType,
|
||||
fileName = fileName,
|
||||
uuid = uuid
|
||||
uuid = clientUuid
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,18 +5,14 @@
|
|||
|
||||
package org.thoughtcrime.securesms.backup.v2.processor
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Hex
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StickerPackSticker
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerRecordReader
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
|
||||
|
||||
|
@ -41,36 +37,12 @@ object StickerBackupProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getStickersFromDatabase(packId: String): List<StickerPackSticker> {
|
||||
val stickers: MutableList<StickerPackSticker> = java.util.ArrayList()
|
||||
|
||||
SignalDatabase.stickers.getStickersForPack(packId).use { cursor ->
|
||||
val reader = StickerRecordReader(cursor)
|
||||
var record: StickerRecord? = reader.next
|
||||
while (record != null) {
|
||||
stickers.add(
|
||||
StickerPackSticker(
|
||||
emoji = record.emoji,
|
||||
id = record.stickerId
|
||||
)
|
||||
)
|
||||
record = reader.next
|
||||
}
|
||||
}
|
||||
return stickers
|
||||
}
|
||||
|
||||
private fun StickerPackRecord.toBackupFrame(): Frame {
|
||||
val packIdBytes = Hex.fromStringCondensed(packId)
|
||||
val packKey = Hex.fromStringCondensed(packKey)
|
||||
val stickers = getStickersFromDatabase(packId)
|
||||
val pack = StickerPack(
|
||||
packId = packIdBytes.toByteString(),
|
||||
packKey = packKey.toByteString(),
|
||||
title = title.orElse(""),
|
||||
author = author.orElse(""),
|
||||
stickers = stickers
|
||||
packKey = packKey.toByteString()
|
||||
)
|
||||
return Frame(stickerPack = pack)
|
||||
}
|
||||
|
|
|
@ -301,15 +301,6 @@ message DistributionList {
|
|||
repeated uint64 memberRecipientIds = 4; // generated recipient id
|
||||
}
|
||||
|
||||
message Identity {
|
||||
bytes serviceId = 1;
|
||||
bytes identityKey = 2;
|
||||
uint64 timestamp = 3;
|
||||
bool firstUse = 4;
|
||||
bool verified = 5;
|
||||
bool nonblockingApproval = 6;
|
||||
}
|
||||
|
||||
message ChatItem {
|
||||
message IncomingMessageDetails {
|
||||
uint64 dateReceived = 1;
|
||||
|
@ -346,6 +337,7 @@ message ChatItem {
|
|||
RemoteDeletedMessage remoteDeletedMessage = 14;
|
||||
ChatUpdateMessage updateMessage = 15;
|
||||
PaymentNotification paymentNotification = 16;
|
||||
GiftBadge giftBadge = 17;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,6 +428,18 @@ message PaymentNotification {
|
|||
|
||||
}
|
||||
|
||||
message GiftBadge {
|
||||
enum State {
|
||||
UNOPENED = 0;
|
||||
OPENED = 1;
|
||||
REDEEMED = 2;
|
||||
FAILED = 3;
|
||||
}
|
||||
|
||||
bytes receiptCredentialPresentation = 1;
|
||||
State state = 2;
|
||||
}
|
||||
|
||||
message ContactAttachment {
|
||||
message Name {
|
||||
optional string givenName = 1;
|
||||
|
@ -501,12 +505,6 @@ message ContactAttachment {
|
|||
optional string organization = 7;
|
||||
}
|
||||
|
||||
message DocumentMessage {
|
||||
Text text = 1;
|
||||
FilePointer document = 2;
|
||||
repeated Reaction reactions = 3;
|
||||
}
|
||||
|
||||
message StickerMessage {
|
||||
Sticker sticker = 1;
|
||||
repeated Reaction reactions = 2;
|
||||
|
@ -551,7 +549,9 @@ message MessageAttachment {
|
|||
FilePointer pointer = 1;
|
||||
Flag flag = 2;
|
||||
bool wasDownloaded = 3;
|
||||
optional bytes uuid = 4;
|
||||
// Cross-client identifier for this attachment among all attachments on the
|
||||
// owning message. See: SignalService.AttachmentPointer.clientUuid.
|
||||
optional bytes clientUuid = 4;
|
||||
}
|
||||
|
||||
message FilePointer {
|
||||
|
@ -1028,17 +1028,8 @@ message GroupExpirationTimerUpdate {
|
|||
message StickerPack {
|
||||
bytes packId = 1;
|
||||
bytes packKey = 2;
|
||||
string title = 3;
|
||||
string author = 4;
|
||||
repeated StickerPackSticker stickers = 5; // First one should be cover sticker.
|
||||
}
|
||||
|
||||
message StickerPackSticker {
|
||||
string emoji = 1;
|
||||
uint32 id = 2;
|
||||
}
|
||||
|
||||
|
||||
message ChatStyle {
|
||||
message Gradient {
|
||||
uint32 angle = 1; // degrees
|
||||
|
|
Loading…
Add table
Reference in a new issue