From cc0ed6ae7b5b11a57b6c68344c2bba2e8082bdc6 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Fri, 22 Nov 2024 03:36:40 +0100 Subject: [PATCH 1/3] feat(backup): support asset and location message content --- .../kotlin/com/wire/backup/data/BackupData.kt | 50 ++++++- .../com/wire/backup/dump/MPBackupExporter.kt | 32 +---- .../wire/backup/ingest/MPBackupImporter.kt | 4 +- .../com/wire/backup/ingest/MPBackupMapper.kt | 128 +++++++++++++++--- .../com/wire/backup/BackupEndToEndTest.kt | 57 ++++++-- .../wire/backup/ingest/MPBackupImporter.kt | 2 +- .../wire/backup/ingest/MPBackupImporter.kt | 2 +- protobuf-codegen/src/main/proto/backup.proto | 26 ++++ 8 files changed, 244 insertions(+), 57 deletions(-) diff --git a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt index 2e1ce6585db..d5f63d2e043 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt @@ -100,6 +100,52 @@ expect fun BackupDateTime.toLongMilliseconds(): Long sealed class BackupMessageContent { data class Text(val text: String) : BackupMessageContent() - // TODO: Not _yet_ implemented - data class Asset(val todo: String) : BackupMessageContent() + data class Asset( + val mimeType: String, + val size: Int, + val name: String?, + val otrKey: ByteArray, + val sha256: ByteArray, + val assetId: String, + val assetToken: String?, + val assetDomain: String?, + val encryption: EncryptionAlgorithm?, + ) : BackupMessageContent() { + enum class EncryptionAlgorithm { + AES_GCM, AES_CBC + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as Asset + + if (!otrKey.contentEquals(other.otrKey)) return false + if (!sha256.contentEquals(other.sha256)) return false + if (assetId != other.assetId) return false + if (assetToken != other.assetToken) return false + if (assetDomain != other.assetDomain) return false + if (encryption != other.encryption) return false + + return true + } + + override fun hashCode(): Int { + var result = otrKey.contentHashCode() + result = 31 * result + sha256.contentHashCode() + result = 31 * result + assetId.hashCode() + result = 31 * result + (assetToken?.hashCode() ?: 0) + result = 31 * result + (assetDomain?.hashCode() ?: 0) + result = 31 * result + (encryption?.hashCode() ?: 0) + return result + } + } + + data class Location( + val longitude: Float, + val latitude: Float, + val name: String?, + val zoom: Int?, + ) : BackupMessageContent() } diff --git a/backup/src/commonMain/kotlin/com/wire/backup/dump/MPBackupExporter.kt b/backup/src/commonMain/kotlin/com/wire/backup/dump/MPBackupExporter.kt index a64ba4ab910..5f8b480c209 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/dump/MPBackupExporter.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/dump/MPBackupExporter.kt @@ -19,17 +19,12 @@ package com.wire.backup.dump import com.wire.backup.data.BackupConversation import com.wire.backup.data.BackupMessage -import com.wire.backup.data.BackupMessageContent import com.wire.backup.data.BackupQualifiedId import com.wire.backup.data.BackupUser -import com.wire.backup.data.toLongMilliseconds import com.wire.backup.data.toProtoModel +import com.wire.backup.ingest.MPBackupMapper import com.wire.kalium.protobuf.backup.BackupData import com.wire.kalium.protobuf.backup.BackupInfo -import com.wire.kalium.protobuf.backup.ExportUser -import com.wire.kalium.protobuf.backup.ExportedConversation -import com.wire.kalium.protobuf.backup.ExportedMessage -import com.wire.kalium.protobuf.backup.ExportedText import kotlinx.datetime.Clock import pbandk.encodeToByteArray import kotlin.experimental.ExperimentalObjCName @@ -47,6 +42,7 @@ import kotlin.native.ShouldRefineInSwift abstract class CommonMPBackupExporter( private val selfUserId: BackupQualifiedId ) { + private val mapper = MPBackupMapper() private val allUsers = mutableListOf() private val allConversations = mutableListOf() private val allMessages = mutableListOf() @@ -80,28 +76,14 @@ abstract class CommonMPBackupExporter( creationTime = Clock.System.now().toEpochMilliseconds(), clientId = "lol" ), - allConversations.map { ExportedConversation(it.id.toProtoModel(), it.name) }, + allConversations.map { + mapper.mapConversationToProtobuf(it) + }, allMessages.map { - ExportedMessage( - id = it.id, - timeIso = it.creationDate.toLongMilliseconds(), - senderUserId = it.senderUserId.toProtoModel(), - senderClientId = it.senderClientId, - conversationId = it.conversationId.toProtoModel(), - content = when (val content = it.content) { - is BackupMessageContent.Asset -> - ExportedMessage.Content.Text(ExportedText("FAKE ASSET")) // TODO: Support assets - is BackupMessageContent.Text -> - ExportedMessage.Content.Text(ExportedText(content.text)) - } - ) + mapper.mapMessageToProtobuf(it) }, allUsers.map { - ExportUser( - id = it.id.toProtoModel(), - name = it.name, - handle = it.handle - ) + mapper.mapUserToProtobuf(it) }, ) return backupData.encodeToByteArray().also { diff --git a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt index 9b03f28e027..0c499769e54 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt @@ -30,8 +30,8 @@ import kotlin.native.ShouldRefineInSwift */ @OptIn(ExperimentalObjCRefinement::class) @JsExport -abstract class CommonMPBackupImporter(selfUserDomain: String) { - private val mapper = MPBackupMapper(selfUserDomain) +abstract class CommonMPBackupImporter { + private val mapper = MPBackupMapper() /** * Attempts to deserialize backed-up data. diff --git a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt index 8f5874b06a1..69b5f2d5993 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt @@ -22,13 +22,72 @@ import com.wire.backup.data.BackupData import com.wire.backup.data.BackupDateTime import com.wire.backup.data.BackupMessage import com.wire.backup.data.BackupMessageContent +import com.wire.backup.data.BackupMessageContent.Asset.EncryptionAlgorithm import com.wire.backup.data.BackupMetadata import com.wire.backup.data.BackupUser +import com.wire.backup.data.toLongMilliseconds import com.wire.backup.data.toModel +import com.wire.backup.data.toProtoModel +import com.wire.kalium.protobuf.backup.ExportUser +import com.wire.kalium.protobuf.backup.ExportedAsset +import com.wire.kalium.protobuf.backup.ExportedConversation +import com.wire.kalium.protobuf.backup.ExportedEncryptionAlgorithm +import com.wire.kalium.protobuf.backup.ExportedLocation +import com.wire.kalium.protobuf.backup.ExportedMessage import com.wire.kalium.protobuf.backup.ExportedMessage.Content +import com.wire.kalium.protobuf.backup.ExportedText +import pbandk.ByteArr import com.wire.kalium.protobuf.backup.BackupData as ProtoBackupData -internal class MPBackupMapper(val selfUserDomain: String) { +internal class MPBackupMapper { + + fun mapUserToProtobuf(it: BackupUser) = ExportUser( + id = it.id.toProtoModel(), + name = it.name, + handle = it.handle + ) + + fun mapMessageToProtobuf(it: BackupMessage) = ExportedMessage( + id = it.id, + timeIso = it.creationDate.toLongMilliseconds(), + senderUserId = it.senderUserId.toProtoModel(), + senderClientId = it.senderClientId, + conversationId = it.conversationId.toProtoModel(), + content = when (val content = it.content) { + is BackupMessageContent.Asset -> + Content.Asset( + ExportedAsset( + content.mimeType, + content.size.toLong(), + content.name, + ByteArr(content.otrKey), + ByteArr(content.sha256), + content.assetId, + content.assetToken, + content.assetDomain, + when (content.encryption) { + EncryptionAlgorithm.AES_GCM -> ExportedEncryptionAlgorithm.BACKUP_AES_GCM + EncryptionAlgorithm.AES_CBC -> ExportedEncryptionAlgorithm.BACKUP_AES_CBC + null -> null + } + ) + ) + + is BackupMessageContent.Text -> + Content.Text(ExportedText(content.text)) + + is BackupMessageContent.Location -> Content.Location( + ExportedLocation( + content.longitude, + content.latitude, + content.name, + content.zoom + ) + ) + } + ) + + fun mapConversationToProtobuf(it: BackupConversation) = ExportedConversation(it.id.toProtoModel(), it.name) fun fromProtoToBackupModel( protobufData: ProtoBackupData @@ -41,28 +100,63 @@ internal class MPBackupMapper(val selfUserDomain: String) { info.clientId ), users.map { user -> - BackupUser(user.id.toModel(), user.name, user.handle) + fromUserProtoToBackupModel(user) }.toTypedArray(), conversations.map { conversation -> - BackupConversation(conversation.id.toModel(), conversation.name) + fromConversationProtoToBackupModel(conversation) }.toTypedArray(), messages.map { message -> - val content = when (val proContent = message.content) { - is Content.Text -> { - BackupMessageContent.Text(proContent.value.content) - } + fromMessageProtoToBackupModel(message) + }.toTypedArray() + ) + } + + private fun fromMessageProtoToBackupModel(message: ExportedMessage): BackupMessage { + val content = when (val protoContent = message.content) { + is Content.Text -> { + BackupMessageContent.Text(protoContent.value.content) + } - null -> TODO() + is Content.Asset -> BackupMessageContent.Asset( + protoContent.value.mimetype, + protoContent.value.protoSize, + protoContent.value.name, + protoContent.value.otrKey.array, + protoContent.value.sha256.array, + protoContent.value.assetId, + protoContent.value.assetToken, + protoContent.value.assetDomain, + protoContent.value.encryption?.let { + when (it) { + ExportedEncryptionAlgorithm.BACKUP_AES_CBC -> EncryptionAlgorithm.AES_CBC + ExportedEncryptionAlgorithm.BACKUP_AES_GCM -> EncryptionAlgorithm.AES_GCM + is ExportedEncryptionAlgorithm.UNRECOGNIZED -> null + } } - BackupMessage( - id = message.id, - conversationId = message.conversationId.toModel(), - senderUserId = message.senderUserId.toModel(), - senderClientId = message.senderClientId, - creationDate = BackupDateTime(message.timeIso), - content = content - ) - }.toTypedArray() + ) + + is Content.Location -> BackupMessageContent.Location( + protoContent.value.longitude, + protoContent.value.latitude, + protoContent.value.name, + protoContent.value.zoom + ) + + null -> throw IllegalArgumentException("Message content cannot be null!") + } + return BackupMessage( + id = message.id, + conversationId = message.conversationId.toModel(), + senderUserId = message.senderUserId.toModel(), + senderClientId = message.senderClientId, + creationDate = BackupDateTime(message.timeIso), + content = content ) } + + private fun fromConversationProtoToBackupModel(conversation: ExportedConversation) = + BackupConversation(conversation.id.toModel(), conversation.name) + + private fun fromUserProtoToBackupModel(user: ExportUser) = + BackupUser(user.id.toModel(), user.name, user.handle) } diff --git a/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt b/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt index f3df4bac36b..1c1617972ee 100644 --- a/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt +++ b/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt @@ -27,33 +27,72 @@ import com.wire.backup.ingest.CommonMPBackupImporter import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertContentEquals +import kotlin.test.assertEquals import kotlin.test.assertIs class BackupEndToEndTest { @Test - fun givenBackedUpMessages_whenRestoring_thenShouldReadTheSameContent() = runTest { + fun givenBackedUpTextMessages_whenRestoring_thenShouldReadTheSameContent() = runTest { + shouldBackupAndRestoreSameContent(BackupMessageContent.Text("Hello from the backup!")) + } + + @Test + fun givenBackedUpAssetMessage_whenRestoring_thenShouldReadTheSameContent() = runTest { + val content = BackupMessageContent.Asset( + mimeType = "image/jpeg", + size = 42, + name = "pudim.jpg", + otrKey = byteArrayOf(31), + sha256 = byteArrayOf(33), + assetId = "assetId", + assetToken = "token", + assetDomain = "domain", + encryption = BackupMessageContent.Asset.EncryptionAlgorithm.AES_GCM + ) + shouldBackupAndRestoreSameContent(content) + } + + @Test + fun givenBackedUpLocationMessage_whenRestoring_thenShouldReadTheSameContent() = runTest { + val content = BackupMessageContent.Location( + longitude = 42f, + latitude = 24f, + name = "Somewhere over the rainbow", + zoom = 13 + ) + shouldBackupAndRestoreSameContent(content) + } + + private fun shouldBackupAndRestoreSameContent(content: BackupMessageContent) { val expectedMessage = BackupMessage( - "messageId", - BackupQualifiedId("value", "domain"), - BackupQualifiedId("senderID", "senderDomain"), - "senderClientId", - BackupDateTime(24232L), - BackupMessageContent.Text("Hello from the backup!") + id = "messageId", + conversationId = BackupQualifiedId("value", "domain"), + senderUserId = BackupQualifiedId("senderID", "senderDomain"), + senderClientId = "senderClientId", + creationDate = BackupDateTime(0L), + content = content, ) val exporter = object : CommonMPBackupExporter(BackupQualifiedId("eghyue", "potato")) {} exporter.addMessage(expectedMessage) val encoded = exporter.serialize() - val importer = object : CommonMPBackupImporter("potato") {} + val importer = object : CommonMPBackupImporter() {} val result = importer.importBackup(encoded) assertIs(result) + val firstMessage = result.backupData.messages.first() + assertEquals(expectedMessage.conversationId, firstMessage.conversationId) + assertEquals(expectedMessage.id, firstMessage.id) + assertEquals(expectedMessage.senderClientId, firstMessage.senderClientId) + assertEquals(expectedMessage.senderUserId, firstMessage.senderUserId) + assertEquals(expectedMessage.creationDate, firstMessage.creationDate) + assertEquals(expectedMessage.content, firstMessage.content) assertContentEquals(arrayOf(expectedMessage), result.backupData.messages) } @Test fun givenBackUpDataIsUnrecognisable_whenRestoring_thenShouldReturnParsingError() = runTest { - val importer = object : CommonMPBackupImporter("potato") {} + val importer = object : CommonMPBackupImporter() {} val result = importer.importBackup(byteArrayOf(0x42, 0x42, 0x42)) assertIs(result) } diff --git a/backup/src/jsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt b/backup/src/jsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt index c69b32a08ad..d4b732e62be 100644 --- a/backup/src/jsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt +++ b/backup/src/jsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt @@ -18,4 +18,4 @@ package com.wire.backup.ingest @JsExport -actual class MPBackupImporter(selfUserDomain: String) : CommonMPBackupImporter(selfUserDomain) +actual class MPBackupImporter : CommonMPBackupImporter() diff --git a/backup/src/nonJsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt b/backup/src/nonJsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt index 88184b50690..7ab21e0900d 100644 --- a/backup/src/nonJsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt +++ b/backup/src/nonJsMain/kotlin/com/wire/backup/ingest/MPBackupImporter.kt @@ -24,7 +24,7 @@ import kotlin.experimental.ExperimentalObjCName import kotlin.native.ObjCName @OptIn(ExperimentalObjCName::class) -actual class MPBackupImporter(selfUserDomain: String) : CommonMPBackupImporter(selfUserDomain) { +actual class MPBackupImporter : CommonMPBackupImporter() { /** * Imports a backup from the specified root path. diff --git a/protobuf-codegen/src/main/proto/backup.proto b/protobuf-codegen/src/main/proto/backup.proto index 71a16948bde..94a19e5ccbe 100644 --- a/protobuf-codegen/src/main/proto/backup.proto +++ b/protobuf-codegen/src/main/proto/backup.proto @@ -60,9 +60,35 @@ message ExportedMessage { required ExportedQualifiedId conversation_id = 5; oneof content { ExportedText text = 6; + ExportedAsset asset = 7; + ExportedLocation location = 8; } } message ExportedText { required string content = 1; } + +enum ExportedEncryptionAlgorithm { + BACKUP_AES_CBC = 0; + BACKUP_AES_GCM = 1; +} + +message ExportedAsset { + required string mimetype = 1; + required int64 size = 2; + optional string name = 3; + required bytes otr_key = 4; + required bytes sha256 = 5; + required string asset_id = 6; + optional string asset_token = 7; + optional string asset_domain = 8; + optional ExportedEncryptionAlgorithm encryption = 9; +} + +message ExportedLocation { + required float longitude = 1; + required float latitude = 2; + optional string name = 3; + optional int32 zoom = 4; +} From 79e988b619fa41427666d9abaeb08695a86ec581 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Schwaab Date: Fri, 22 Nov 2024 16:17:00 +0100 Subject: [PATCH 2/3] chore: add web primary key for easier debugging --- .../commonMain/kotlin/com/wire/backup/data/BackupData.kt | 4 +++- .../kotlin/com/wire/backup/ingest/MPBackupMapper.kt | 6 ++++-- protobuf-codegen/src/main/proto/backup.proto | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt index d5f63d2e043..2fe20c4414a 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt @@ -88,7 +88,9 @@ data class BackupMessage( val senderUserId: BackupQualifiedId, val senderClientId: String, val creationDate: BackupDateTime, - val content: BackupMessageContent + val content: BackupMessageContent, + @Deprecated("Used only by the Webteam in order to simplify debugging", ReplaceWith("")) + val webPrimaryKey: Int? = null, ) expect class BackupDateTime diff --git a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt index 69b5f2d5993..97bce967771 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt @@ -84,7 +84,8 @@ internal class MPBackupMapper { content.zoom ) ) - } + }, + webPk = it.webPrimaryKey?.toLong() ) fun mapConversationToProtobuf(it: BackupConversation) = ExportedConversation(it.id.toProtoModel(), it.name) @@ -150,7 +151,8 @@ internal class MPBackupMapper { senderUserId = message.senderUserId.toModel(), senderClientId = message.senderClientId, creationDate = BackupDateTime(message.timeIso), - content = content + content = content, + webPrimaryKey = message.webPk?.toInt() ) } diff --git a/protobuf-codegen/src/main/proto/backup.proto b/protobuf-codegen/src/main/proto/backup.proto index 94a19e5ccbe..571fc492103 100644 --- a/protobuf-codegen/src/main/proto/backup.proto +++ b/protobuf-codegen/src/main/proto/backup.proto @@ -63,6 +63,7 @@ message ExportedMessage { ExportedAsset asset = 7; ExportedLocation location = 8; } + optional int64 web_pk = 9; // WARNING: Only for development. Web-specific for assisting with debugging } message ExportedText { From 6157bf2bba68b025049d37b161e9d434a00f08fe Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Fri, 29 Nov 2024 11:14:47 -0300 Subject: [PATCH 3/3] feat(backup): add more types - metadata assets (WPB-14589) (#3128) * feat: add metadata for assetd * feat: add metadata for assetd * fix: detekt * feat: metadata logs * feat: generic metadata for images --- backup/build.gradle.kts | 1 + .../kotlin/com/wire/backup/data/BackupData.kt | 26 ++++ .../com/wire/backup/ingest/MPBackupMapper.kt | 139 +++++++++++++----- .../com/wire/backup/BackupEndToEndTest.kt | 9 +- protobuf-codegen/src/main/proto/backup.proto | 27 ++++ 5 files changed, 164 insertions(+), 38 deletions(-) diff --git a/backup/build.gradle.kts b/backup/build.gradle.kts index b3a04f69097..69fe6886ac0 100644 --- a/backup/build.gradle.kts +++ b/backup/build.gradle.kts @@ -59,6 +59,7 @@ kotlin { // Libsodium implementation(libs.libsodiumBindingsMP) + api(libs.kermit) } } val commonTest by getting { diff --git a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt index 2fe20c4414a..f0d59107750 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/data/BackupData.kt @@ -112,11 +112,35 @@ sealed class BackupMessageContent { val assetToken: String?, val assetDomain: String?, val encryption: EncryptionAlgorithm?, + val metaData: AssetMetadata?, ) : BackupMessageContent() { enum class EncryptionAlgorithm { AES_GCM, AES_CBC } + sealed class AssetMetadata { + data class Image( + val width: Int, + val height: Int, + val tag: String? + ) : AssetMetadata() + + data class Video( + val width: Int?, + val height: Int?, + val duration: Long?, + ) : AssetMetadata() + + data class Audio( + val normalization: ByteArray?, + val duration: Long?, + ) : AssetMetadata() + + data class Generic( + val name: String?, + ) : AssetMetadata() + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false @@ -129,6 +153,7 @@ sealed class BackupMessageContent { if (assetToken != other.assetToken) return false if (assetDomain != other.assetDomain) return false if (encryption != other.encryption) return false + if (metaData != other.metaData) return false return true } @@ -140,6 +165,7 @@ sealed class BackupMessageContent { result = 31 * result + (assetToken?.hashCode() ?: 0) result = 31 * result + (assetDomain?.hashCode() ?: 0) result = 31 * result + (encryption?.hashCode() ?: 0) + result = 31 * result + (metaData?.hashCode() ?: 0) return result } } diff --git a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt index 97bce967771..94881eafc0a 100644 --- a/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt +++ b/backup/src/commonMain/kotlin/com/wire/backup/ingest/MPBackupMapper.kt @@ -17,6 +17,7 @@ */ package com.wire.backup.ingest +import co.touchlab.kermit.Logger import com.wire.backup.data.BackupConversation import com.wire.backup.data.BackupData import com.wire.backup.data.BackupDateTime @@ -30,12 +31,16 @@ import com.wire.backup.data.toModel import com.wire.backup.data.toProtoModel import com.wire.kalium.protobuf.backup.ExportUser import com.wire.kalium.protobuf.backup.ExportedAsset +import com.wire.kalium.protobuf.backup.ExportedAudioMetaData import com.wire.kalium.protobuf.backup.ExportedConversation import com.wire.kalium.protobuf.backup.ExportedEncryptionAlgorithm +import com.wire.kalium.protobuf.backup.ExportedGenericMetaData +import com.wire.kalium.protobuf.backup.ExportedImageMetaData import com.wire.kalium.protobuf.backup.ExportedLocation import com.wire.kalium.protobuf.backup.ExportedMessage import com.wire.kalium.protobuf.backup.ExportedMessage.Content import com.wire.kalium.protobuf.backup.ExportedText +import com.wire.kalium.protobuf.backup.ExportedVideoMetaData import pbandk.ByteArr import com.wire.kalium.protobuf.backup.BackupData as ProtoBackupData @@ -47,46 +52,83 @@ internal class MPBackupMapper { handle = it.handle ) - fun mapMessageToProtobuf(it: BackupMessage) = ExportedMessage( - id = it.id, - timeIso = it.creationDate.toLongMilliseconds(), - senderUserId = it.senderUserId.toProtoModel(), - senderClientId = it.senderClientId, - conversationId = it.conversationId.toProtoModel(), - content = when (val content = it.content) { - is BackupMessageContent.Asset -> - Content.Asset( - ExportedAsset( - content.mimeType, - content.size.toLong(), - content.name, - ByteArr(content.otrKey), - ByteArr(content.sha256), - content.assetId, - content.assetToken, - content.assetDomain, - when (content.encryption) { - EncryptionAlgorithm.AES_GCM -> ExportedEncryptionAlgorithm.BACKUP_AES_GCM - EncryptionAlgorithm.AES_CBC -> ExportedEncryptionAlgorithm.BACKUP_AES_CBC - null -> null - } + @Suppress("LongMethod") + fun mapMessageToProtobuf(it: BackupMessage): ExportedMessage { + return ExportedMessage( + id = it.id, + timeIso = it.creationDate.toLongMilliseconds(), + senderUserId = it.senderUserId.toProtoModel(), + senderClientId = it.senderClientId, + conversationId = it.conversationId.toProtoModel(), + content = when (val content = it.content) { + is BackupMessageContent.Asset -> { + Logger.d("MPBackupMapper") { "Mapping asset message to protobuf: ${content.metaData}" } + Content.Asset( + ExportedAsset( + content.mimeType, + content.size.toLong(), + content.name, + ByteArr(content.otrKey), + ByteArr(content.sha256), + content.assetId, + content.assetToken, + content.assetDomain, + when (content.encryption) { + EncryptionAlgorithm.AES_GCM -> ExportedEncryptionAlgorithm.BACKUP_AES_GCM + EncryptionAlgorithm.AES_CBC -> ExportedEncryptionAlgorithm.BACKUP_AES_CBC + null -> null + }, + content.metaData?.let { + when (it) { + is BackupMessageContent.Asset.AssetMetadata.Audio -> + ExportedAsset.MetaData.Audio( + ExportedAudioMetaData( + it.duration, + it.normalization?.let { ByteArr(it) } + ) + ) + + is BackupMessageContent.Asset.AssetMetadata.Image -> + ExportedAsset.MetaData.Image( + ExportedImageMetaData( + it.width, + it.height, + it.tag + ) + ) + + is BackupMessageContent.Asset.AssetMetadata.Video -> + ExportedAsset.MetaData.Video( + ExportedVideoMetaData( + it.width, + it.height, + it.duration + ) + ) + + is BackupMessageContent.Asset.AssetMetadata.Generic -> + ExportedAsset.MetaData.Generic(ExportedGenericMetaData(it.name)) + } + } + ) ) - ) + } - is BackupMessageContent.Text -> - Content.Text(ExportedText(content.text)) + is BackupMessageContent.Text -> + Content.Text(ExportedText(content.text)) - is BackupMessageContent.Location -> Content.Location( - ExportedLocation( - content.longitude, - content.latitude, - content.name, - content.zoom + is BackupMessageContent.Location -> Content.Location( + ExportedLocation( + content.longitude, + content.latitude, + content.name, + content.zoom + ) ) - ) - }, - webPk = it.webPrimaryKey?.toLong() - ) + }, + webPk = it.webPrimaryKey?.toLong() + ) + } fun mapConversationToProtobuf(it: BackupConversation) = ExportedConversation(it.id.toProtoModel(), it.name) @@ -112,6 +154,7 @@ internal class MPBackupMapper { ) } + @Suppress("LongMethod") private fun fromMessageProtoToBackupModel(message: ExportedMessage): BackupMessage { val content = when (val protoContent = message.content) { is Content.Text -> { @@ -133,6 +176,30 @@ internal class MPBackupMapper { ExportedEncryptionAlgorithm.BACKUP_AES_GCM -> EncryptionAlgorithm.AES_GCM is ExportedEncryptionAlgorithm.UNRECOGNIZED -> null } + }, + protoContent.value.metaData?.let { + when (it) { + is ExportedAsset.MetaData.Audio -> BackupMessageContent.Asset.AssetMetadata.Audio( + it.value.normalizedLoudness?.array, + it.value.durationInMillis + ) + + is ExportedAsset.MetaData.Image -> BackupMessageContent.Asset.AssetMetadata.Image( + it.value.width, + it.value.height, + it.value.tag + ) + + is ExportedAsset.MetaData.Video -> BackupMessageContent.Asset.AssetMetadata.Video( + it.value.width, + it.value.height, + it.value.durationInMillis + ) + + is ExportedAsset.MetaData.Generic -> BackupMessageContent.Asset.AssetMetadata.Generic( + it.value.name + ) + } } ) diff --git a/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt b/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt index 1c1617972ee..77b3bc6942c 100644 --- a/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt +++ b/backup/src/commonTest/kotlin/com/wire/backup/BackupEndToEndTest.kt @@ -41,14 +41,19 @@ class BackupEndToEndTest { fun givenBackedUpAssetMessage_whenRestoring_thenShouldReadTheSameContent() = runTest { val content = BackupMessageContent.Asset( mimeType = "image/jpeg", - size = 42, + size = 64, name = "pudim.jpg", otrKey = byteArrayOf(31), sha256 = byteArrayOf(33), assetId = "assetId", assetToken = "token", assetDomain = "domain", - encryption = BackupMessageContent.Asset.EncryptionAlgorithm.AES_GCM + encryption = BackupMessageContent.Asset.EncryptionAlgorithm.AES_GCM, + metaData = BackupMessageContent.Asset.AssetMetadata.Video( + duration = 42, + width = 800, + height = 600, + ) ) shouldBackupAndRestoreSameContent(content) } diff --git a/protobuf-codegen/src/main/proto/backup.proto b/protobuf-codegen/src/main/proto/backup.proto index 571fc492103..334aa1627b0 100644 --- a/protobuf-codegen/src/main/proto/backup.proto +++ b/protobuf-codegen/src/main/proto/backup.proto @@ -85,6 +85,33 @@ message ExportedAsset { optional string asset_token = 7; optional string asset_domain = 8; optional ExportedEncryptionAlgorithm encryption = 9; + oneof meta_data { + ExportedImageMetaData image = 10; + ExportedVideoMetaData video = 11; + ExportedAudioMetaData audio = 12; + ExportedGenericMetaData generic = 13; + } +} + + message ExportedImageMetaData { + required int32 width = 1; + required int32 height = 2; + optional string tag = 3; +} + +message ExportedVideoMetaData { + optional int32 width = 1; + optional int32 height = 2; + optional uint64 duration_in_millis = 3; +} + +message ExportedAudioMetaData { + optional uint64 duration_in_millis = 1; + optional bytes normalized_loudness = 2; // each byte represent one loudness value as a byte (char) value. +} + +message ExportedGenericMetaData { + optional string name = 1; } message ExportedLocation {