From 2b6e0ac33d391f2ea1b6e5d704fbe4732562c59c Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 11 Sep 2024 15:16:23 +0300 Subject: [PATCH] feat: Save code for EncryptionError [WPB-1795] (#2989) * feat: Save code for EncryptionError * Fixed tests * Improved some vals naming * Review fixes * Code style fix --- .../ProteusClientCryptoBoxImpl.kt | 4 +- .../ProteusClientCoreCryptoImpl.kt | 8 +- .../ProteusClientCoreCryptoImpl.kt | 19 +- .../exceptions/ProteusException.kt | 12 +- .../ProteusClientCryptoBoxImpl.kt | 2 +- .../ProteusClientCryptoBoxImpl.kt | 4 +- .../wire/kalium/logic/data/message/Message.kt | 1 + .../logic/data/message/MessageContent.kt | 1 + .../com/wire/kalium/logic/CoreFailure.kt | 2 +- .../logic/data/message/MessageMapper.kt | 2 + .../message/NewMessageEventHandler.kt | 3 + .../message/ProteusMessageUnpacker.kt | 3 +- .../corefailure/WrapProteusRequestTest.kt | 2 +- .../logic/data/prekey/PreKeyRepositoryTest.kt | 2 +- .../client/ClientFingerprintUseCaseTest.kt | 4 +- .../message/MessageEnvelopeCreatorTest.kt | 8 +- .../feature/message/SessionEstablisherTest.kt | 2 +- .../message/NewMessageEventHandlerTest.kt | 6 +- .../wire/kalium/persistence/DumpContent.sq | 3 +- .../kalium/persistence/MessageDetailsView.sq | 1 + .../com/wire/kalium/persistence/Messages.sq | 5 +- .../src/commonMain/db_user/migrations/86.sqm | 166 ++++++++++++++++++ .../persistence/dao/message/MessageEntity.kt | 1 + .../dao/message/MessageInsertExtension.kt | 1 + .../persistence/dao/message/MessageMapper.kt | 2 + .../persistence/dao/message/MessageDAOTest.kt | 5 +- .../dao/message/MessageMapperTest.kt | 2 + 27 files changed, 238 insertions(+), 33 deletions(-) create mode 100644 persistence/src/commonMain/db_user/migrations/86.sqm diff --git a/cryptography/src/androidMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt b/cryptography/src/androidMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt index 4414b91a749..e1e929c1d13 100644 --- a/cryptography/src/androidMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt +++ b/cryptography/src/androidMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt @@ -181,9 +181,9 @@ class ProteusClientCryptoBoxImpl constructor( try { return b() } catch (e: CryptoException) { - throw ProteusException(e.message, fromCryptoException(e), e.cause) + throw ProteusException(e.message, fromCryptoException(e), e.code.ordinal, e.cause) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause) } } diff --git a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/ProteusClientCoreCryptoImpl.kt b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/ProteusClientCoreCryptoImpl.kt index 0d9cf4fa155..32d1b107f78 100644 --- a/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/ProteusClientCoreCryptoImpl.kt +++ b/cryptography/src/appleMain/kotlin/com/wire/kalium/cryptography/ProteusClientCoreCryptoImpl.kt @@ -134,9 +134,9 @@ class ProteusClientCoreCryptoImpl private constructor(private val coreCrypto: Co return b() } catch (e: CryptoException) { // TODO underlying proteus error is not exposed atm - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, null) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, null) } } @@ -190,9 +190,9 @@ class ProteusClientCoreCryptoImpl private constructor(private val coreCrypto: Co coreCrypto.proteusInit() return ProteusClientCoreCryptoImpl(coreCrypto) } catch (e: CryptoException) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause) } } } diff --git a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt index 9a373797466..fdb19c2a692 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt @@ -140,9 +140,15 @@ class ProteusClientCoreCryptoImpl private constructor( try { return b() } catch (e: CoreCryptoException) { - throw ProteusException(e.message, ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), e) + val proteusLastErrorCode = coreCrypto.proteusLastErrorCode() + throw ProteusException( + e.message, + ProteusException.fromProteusCode(proteusLastErrorCode.toInt()), + proteusLastErrorCode.toInt(), + e + ) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e) } } @@ -188,9 +194,14 @@ class ProteusClientCoreCryptoImpl private constructor( coreCrypto.proteusInit() return ProteusClientCoreCryptoImpl(coreCrypto) } catch (e: CoreCryptoException) { - throw ProteusException(e.message, ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), e.cause) + throw ProteusException( + e.message, + ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), + coreCrypto.proteusLastErrorCode().toInt(), + e.cause + ) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause) } } } diff --git a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt index ca85a709fd5..5a67b9d9e35 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt @@ -18,9 +18,14 @@ package com.wire.kalium.cryptography.exceptions -class ProteusException(message: String?, val code: Code, cause: Throwable? = null) : Exception(message, cause) { +class ProteusException(message: String?, val code: Code, val intCode: Int?, cause: Throwable? = null) : Exception(message, cause) { - constructor(message: String?, code: Int, cause: Throwable? = null) : this(message, fromNativeCode(code), cause) + constructor(message: String?, code: Int, cause: Throwable? = null) : this( + message, + fromNativeCode(code), + code, + cause + ) enum class Code { /** @@ -149,6 +154,9 @@ class ProteusException(message: String?, val code: Code, cause: Throwable? = nul } companion object { + + const val SESSION_NOT_FOUND_INT = 2 + @Suppress("MagicNumber") fun fromNativeCode(code: Int): Code { return when (code) { diff --git a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt index f07272f107e..8ecdf74ca93 100644 --- a/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt +++ b/cryptography/src/jsMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt @@ -80,7 +80,7 @@ class ProteusClientCryptoBoxImpl : ProteusClient { if (preKey != null) { return toPreKey(box.getIdentity().public_key, preKey) } else { - throw ProteusException("Local identity doesn't exist", ProteusException.Code.UNKNOWN_ERROR) + throw ProteusException("Local identity doesn't exist", ProteusException.Code.UNKNOWN_ERROR, null, null) } } diff --git a/cryptography/src/jvmMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt b/cryptography/src/jvmMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt index 33dbbde607a..01cd3de41fd 100644 --- a/cryptography/src/jvmMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt +++ b/cryptography/src/jvmMain/kotlin/com/wire/kalium/cryptography/ProteusClientCryptoBoxImpl.kt @@ -91,7 +91,7 @@ class ProteusClientCryptoBoxImpl constructor( override suspend fun encrypt(message: ByteArray, sessionId: CryptoSessionId): ByteArray { return wrapException { box.encryptFromSession(sessionId.value, message) - }?.let { it } ?: throw ProteusException(null, ProteusException.Code.SESSION_NOT_FOUND) + } ?: throw ProteusException(null, ProteusException.Code.SESSION_NOT_FOUND, ProteusException.SESSION_NOT_FOUND_INT) } override suspend fun encryptBatched(message: ByteArray, sessionIds: List): Map { @@ -127,7 +127,7 @@ class ProteusClientCryptoBoxImpl constructor( } catch (e: CryptoException) { throw ProteusException(e.message, e.code.ordinal, e.cause) } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) + throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause) } } diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt index d28cfa435f4..a188c0dabe5 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt @@ -120,6 +120,7 @@ sealed interface Message { is MessageContent.FailedDecryption -> { mutableMapOf( typeKey to "failedDecryption", + "code" to "${content.errorCode}", "size" to "${content.encodedData?.size}", ) } diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt index f38a9b074cb..56103c2371f 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt @@ -350,6 +350,7 @@ sealed interface MessageContent { data class FailedDecryption( val encodedData: ByteArray? = null, + val errorCode: Int? = null, val isDecryptionResolved: Boolean, val senderUserId: UserId, val clientId: ClientId? = null diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt index 6ce16825226..d6adca0a39f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/CoreFailure.kt @@ -336,7 +336,7 @@ internal inline fun wrapProteusRequest(proteusRequest: () -> T): Eithe |"cause": ${e.cause} }" """.trimMargin() ) kaliumLogger.e(e.stackTraceToString()) - Either.Left(ProteusFailure(ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause))) + Either.Left(ProteusFailure(ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e.cause))) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt index 32044174038..c28f15eea3a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageMapper.kt @@ -375,6 +375,7 @@ class MessageMapperImpl( // We store the encoded data in case we decide to try to decrypt them again in the future is MessageContent.FailedDecryption -> MessageEntityContent.FailedDecryption( regularMessage.encodedData, + regularMessage.errorCode, regularMessage.isDecryptionResolved, regularMessage.senderUserId.toDao(), regularMessage.clientId?.value @@ -611,6 +612,7 @@ fun MessageEntityContent.Regular.toMessageContent(hidden: Boolean, selfUserId: U is MessageEntityContent.Unknown -> MessageContent.Unknown(this.typeName, this.encodedData, hidden) is MessageEntityContent.FailedDecryption -> MessageContent.FailedDecryption( this.encodedData, + this.code, this.isDecryptionResolved, this.senderUserId.toModel(), ClientId(this.senderClientId.orEmpty()) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandler.kt index ff54b70d4b1..8aa873e4e7a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandler.kt @@ -74,6 +74,8 @@ internal class NewMessageEventHandlerImpl( logger.e("Failed to decrypt event: ${logMap.toJsonElement()}") + val errorCode = if (it is ProteusFailure) it.proteusException.intCode else null + applicationMessageHandler.handleDecryptionError( eventId = event.id, conversationId = event.conversationId, @@ -82,6 +84,7 @@ internal class NewMessageEventHandlerImpl( senderClientId = event.senderClientId, content = MessageContent.FailedDecryption( encodedData = event.encryptedExternalContent?.data, + errorCode = errorCode, isDecryptionResolved = false, senderUserId = event.senderUserId, clientId = ClientId(event.senderClientId.value) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ProteusMessageUnpacker.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ProteusMessageUnpacker.kt index 7d039df3d23..5042c90bb76 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ProteusMessageUnpacker.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ProteusMessageUnpacker.kt @@ -79,7 +79,8 @@ internal class ProteusMessageUnpackerImpl( is ProteusFailure -> { val loggableException = - "{ \"code\": \"${it.proteusException.code.name}\", \"message\": \"${it.proteusException.message}\", " + + "{ \"code\": \"${it.proteusException.code.name}\", \"intCode\": \"${it.proteusException.intCode}\"," + + " \"message\": \"${it.proteusException.message}\", " + "\"error\": \"${it.proteusException.stackTraceToString()}\"," + "\"senderClientId\": \"${event.senderClientId.value.obfuscateId()}\"," + "\"senderUserId\": \"${event.senderUserId.value.obfuscateId()}\"," + diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/corefailure/WrapProteusRequestTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/corefailure/WrapProteusRequestTest.kt index 6c2b0522d26..cb4ea1bff92 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/corefailure/WrapProteusRequestTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/corefailure/WrapProteusRequestTest.kt @@ -40,7 +40,7 @@ class WrapProteusRequestTest { @Test fun whenApiRequestReturnNoInternetConnection_thenCorrectErrorIsPropagated() { - val expected = ProteusException(null, ProteusException.Code.PANIC, RuntimeException()) + val expected = ProteusException(null, ProteusException.Code.PANIC, 15, RuntimeException()) val actual = wrapProteusRequest { throw expected } assertIs>(actual) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/prekey/PreKeyRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/prekey/PreKeyRepositoryTest.kt index 3a3df9fee55..a185091158c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/prekey/PreKeyRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/prekey/PreKeyRepositoryTest.kt @@ -177,7 +177,7 @@ class PreKeyRepositoryTest { @Test fun givenCreatingSessionsThrows_whenPreparingSessions_thenItShouldFail() = runTest { - val exception = ProteusException("PANIC!!!11!eleven!", ProteusException.Code.PANIC) + val exception = ProteusException("PANIC!!!11!eleven!", ProteusException.Code.PANIC, 15) val preKey = PreKeyDTO(42, "encodedData") val userPreKeysResult = diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ClientFingerprintUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ClientFingerprintUseCaseTest.kt index 07e226dfa8a..3750fb6f91a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ClientFingerprintUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ClientFingerprintUseCaseTest.kt @@ -95,7 +95,7 @@ class ClientFingerprintUseCaseTest { @Test fun givenProteusException_whenGettingRemoteFingerPrint_thenErrorIsReturned() = runTest { - val error = ProteusException(null, ProteusException.Code.DECODE_ERROR) + val error = ProteusException(null, ProteusException.Code.DECODE_ERROR, 3) val userId = TestUser.USER_ID val clientId = TestClient.CLIENT_ID @@ -147,7 +147,7 @@ class ClientFingerprintUseCaseTest { .invokes { _ -> if (getSessionCalled == 0) { getSessionCalled++ - throw ProteusException(null, ProteusException.Code.SESSION_NOT_FOUND) + throw ProteusException(null, ProteusException.Code.SESSION_NOT_FOUND, 2) } secondTimeResult } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt index ef7a1c9a643..6f515c2f488 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MessageEnvelopeCreatorTest.kt @@ -338,7 +338,7 @@ class MessageEnvelopeCreatorTest { @Test fun givenProteusThrowsDuringEncryption_whenCreatingEnvelope_thenTheFailureShouldBePropagated() = runTest { - val exception = ProteusException("OOPS", ProteusException.Code.PANIC) + val exception = ProteusException("OOPS", ProteusException.Code.PANIC, 15) coEvery { proteusClient.encryptBatched(any(), any()) }.throws(exception) @@ -368,7 +368,7 @@ class MessageEnvelopeCreatorTest { fun givenProteusThrowsDuringEncryption_whenCreatingEnvelope_thenNoMoreEncryptionsShouldBeAttempted() = runTest { coEvery { proteusClient.encryptBatched(any(), any()) - }.throws(ProteusException("OOPS", ProteusException.Code.PANIC)) + }.throws(ProteusException("OOPS", ProteusException.Code.PANIC, 15)) every { @@ -590,7 +590,7 @@ class MessageEnvelopeCreatorTest { @Test fun givenProteusThrowsDuringEncryption_whenCreatingBroadcastEnvelope_thenTheFailureShouldBePropagated() = runTest { - val exception = ProteusException("OOPS", ProteusException.Code.PANIC) + val exception = ProteusException("OOPS", ProteusException.Code.PANIC, 15) coEvery { proteusClient.encryptBatched(any(), any()) }.throws(exception) @@ -610,7 +610,7 @@ class MessageEnvelopeCreatorTest { fun givenProteusThrowsDuringEncryption_whenCreatingBroadcastEnvelope_thenNoMoreEncryptionsShouldBeAttempted() = runTest { coEvery { proteusClient.encryptBatched(any(), any()) - }.throws(ProteusException("OOPS", ProteusException.Code.PANIC)) + }.throws(ProteusException("OOPS", ProteusException.Code.PANIC, 15)) every { protoContentMapper.encodeToProtobuf(any()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/SessionEstablisherTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/SessionEstablisherTest.kt index f88bf4bb836..3b8c40b565b 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/SessionEstablisherTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/SessionEstablisherTest.kt @@ -66,7 +66,7 @@ class SessionEstablisherTest { @Test fun givenProteusClientThrowsWhenCheckingSession_whenPreparingSessions_thenItShouldFail() = runTest { - val exception = ProteusException("PANIC!!!11!eleven!", ProteusException.Code.PANIC) + val exception = ProteusException("PANIC!!!11!eleven!", ProteusException.Code.PANIC, 15) val (_, sessionEstablisher) = Arrangement() .withDoesSessionExistThrows(exception) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt index 50009a3f18c..f6ce3981971 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/NewMessageEventHandlerTest.kt @@ -76,7 +76,8 @@ class NewMessageEventHandlerTest { ProteusFailure( ProteusException( message = null, - code = ProteusException.Code.DUPLICATE_MESSAGE + code = ProteusException.Code.DUPLICATE_MESSAGE, + intCode = 7 ) ) ) @@ -104,7 +105,8 @@ class NewMessageEventHandlerTest { ProteusFailure( ProteusException( message = null, - code = ProteusException.Code.INVALID_SIGNATURE + code = ProteusException.Code.INVALID_SIGNATURE, + intCode = 5 ) ) ) diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/DumpContent.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/DumpContent.sq index 2393310f240..4ebb5bbd3a3 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/DumpContent.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/DumpContent.sq @@ -76,7 +76,8 @@ INSERT INTO MessageFailedToDecryptContent SELECT local_db.MessageFailedToDecryptContent.message_id, local_db.MessageFailedToDecryptContent.conversation_id, local_db.MessageFailedToDecryptContent.unknown_encoded_data, - local_db.MessageFailedToDecryptContent.is_decryption_resolved + local_db.MessageFailedToDecryptContent.is_decryption_resolved, + local_db.MessageFailedToDecryptContent.error_code FROM local_db.MessageFailedToDecryptContent LEFT JOIN selfdelete_message_id ON local_db.MessageFailedToDecryptContent.message_id = selfdelete_message_id.id diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq index df890ee58e8..06e927cdaf3 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/MessageDetailsView.sq @@ -69,6 +69,7 @@ RestrictedAssetContent.asset_mime_type AS restrictedAssetMimeType, RestrictedAssetContent.asset_size AS restrictedAssetSize, RestrictedAssetContent.asset_name AS restrictedAssetName, FailedToDecryptContent.unknown_encoded_data AS failedToDecryptData, +FailedToDecryptContent.error_code AS decryptionErrorCode, FailedToDecryptContent.is_decryption_resolved AS isDecryptionResolved, ConversationNameChangedContent.conversation_name AS conversationName, '{' || IFNULL( diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq index 82764a8a468..1f9192d5756 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq @@ -160,6 +160,7 @@ CREATE TABLE MessageFailedToDecryptContent ( unknown_encoded_data BLOB, is_decryption_resolved INTEGER AS Boolean NOT NULL DEFAULT(0), + error_code INTEGER, FOREIGN KEY (message_id, conversation_id) REFERENCES Message(id, conversation_id) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY (message_id, conversation_id) @@ -410,8 +411,8 @@ INSERT OR IGNORE INTO MessageUnknownContent(message_id, conversation_id, unknown VALUES(?, ?, ?, ?); insertFailedDecryptionMessageContent: -INSERT OR IGNORE INTO MessageFailedToDecryptContent(message_id, conversation_id, unknown_encoded_data) -VALUES(?, ?, ?); +INSERT OR IGNORE INTO MessageFailedToDecryptContent(message_id, conversation_id, unknown_encoded_data, error_code) +VALUES(?, ?, ?, ?); insertMissedCallMessage: INSERT OR IGNORE INTO MessageMissedCallContent(message_id, conversation_id, caller_id) diff --git a/persistence/src/commonMain/db_user/migrations/86.sqm b/persistence/src/commonMain/db_user/migrations/86.sqm new file mode 100644 index 00000000000..92bfc8fb8cf --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/86.sqm @@ -0,0 +1,166 @@ +ALTER TABLE MessageFailedToDecryptContent ADD COLUMN error_code INTEGER; + +DROP VIEW IF EXISTS MessageDetailsView; + +CREATE VIEW IF NOT EXISTS MessageDetailsView +AS SELECT +Message.id AS id, +Message.conversation_id AS conversationId, +Message.content_type AS contentType, +Message.creation_date AS date, +Message.sender_user_id AS senderUserId, +Message.sender_client_id AS senderClientId, +Message.status AS status, +Message.last_edit_date AS lastEditTimestamp, +Message.visibility AS visibility, +Message.expects_read_confirmation AS expectsReadConfirmation, +Message.expire_after_millis AS expireAfterMillis, +Message.self_deletion_end_date AS selfDeletionEndDate, +IFNULL ((SELECT COUNT (*) FROM Receipt WHERE message_id = Message.id AND type = 'READ'), 0) AS readCount, +UserDetails.name AS senderName, +UserDetails.handle AS senderHandle, +UserDetails.email AS senderEmail, +UserDetails.phone AS senderPhone, +UserDetails.accent_id AS senderAccentId, +UserDetails.team AS senderTeamId, +UserDetails.connection_status AS senderConnectionStatus, +UserDetails.preview_asset_id AS senderPreviewAssetId, +UserDetails.complete_asset_id AS senderCompleteAssetId, +UserDetails.user_availability_status AS senderAvailabilityStatus, +UserDetails.user_type AS senderUserType, +UserDetails.bot_service AS senderBotService, +UserDetails.deleted AS senderIsDeleted, +UserDetails.expires_at AS senderExpiresAt, +UserDetails.defederated AS senderDefederated, +UserDetails.supported_protocols AS senderSupportedProtocols, +UserDetails.active_one_on_one_conversation_id AS senderActiveOneOnOneConversationId, +UserDetails.is_proteus_verified AS senderIsProteusVerified, +UserDetails.is_under_legal_hold AS senderIsUnderLegalHold, +(Message.sender_user_id == SelfUser.id) AS isSelfMessage, +TextContent.text_body AS text, +TextContent.is_quoting_self AS isQuotingSelfUser, +AssetContent.asset_size AS assetSize, +AssetContent.asset_name AS assetName, +AssetContent.asset_mime_type AS assetMimeType, +AssetContent.asset_otr_key AS assetOtrKey, +AssetContent.asset_sha256 AS assetSha256, +AssetContent.asset_id AS assetId, +AssetContent.asset_token AS assetToken, +AssetContent.asset_domain AS assetDomain, +AssetContent.asset_encryption_algorithm AS assetEncryptionAlgorithm, +AssetContent.asset_width AS assetWidth, +AssetContent.asset_height AS assetHeight, +AssetContent.asset_duration_ms AS assetDuration, +AssetContent.asset_normalized_loudness AS assetNormalizedLoudness, +MissedCallContent.caller_id AS callerId, +MemberChangeContent.member_change_list AS memberChangeList, +MemberChangeContent.member_change_type AS memberChangeType, +UnknownContent.unknown_type_name AS unknownContentTypeName, +UnknownContent.unknown_encoded_data AS unknownContentData, +RestrictedAssetContent.asset_mime_type AS restrictedAssetMimeType, +RestrictedAssetContent.asset_size AS restrictedAssetSize, +RestrictedAssetContent.asset_name AS restrictedAssetName, +FailedToDecryptContent.unknown_encoded_data AS failedToDecryptData, +FailedToDecryptContent.error_code AS decryptionErrorCode, +FailedToDecryptContent.is_decryption_resolved AS isDecryptionResolved, +ConversationNameChangedContent.conversation_name AS conversationName, +'{' || IFNULL( + (SELECT GROUP_CONCAT('"' || emoji || '":' || count) + FROM ( + SELECT COUNT(*) count, Reaction.emoji emoji + FROM Reaction + WHERE Reaction.message_id = Message.id + AND Reaction.conversation_id = Message.conversation_id + GROUP BY Reaction.emoji + )), + '') +|| '}' AS allReactionsJson, +IFNULL( + (SELECT '[' || GROUP_CONCAT('"' || Reaction.emoji || '"') || ']' + FROM Reaction + WHERE Reaction.message_id = Message.id + AND Reaction.conversation_id = Message.conversation_id + AND Reaction.sender_id = SelfUser.id + ), + '[]' +) AS selfReactionsJson, +IFNULL( + (SELECT '[' || GROUP_CONCAT( + '{"start":' || start || ', "length":' || length || + ', "userId":{"value":"' || replace(substr(user_id, 0, instr(user_id, '@')), '@', '') || '"' || + ',"domain":"' || replace(substr(user_id, instr(user_id, '@')+1, length(user_id)), '@', '') || '"' || + '}' || '}') || ']' + FROM MessageMention + WHERE MessageMention.message_id = Message.id + AND MessageMention.conversation_id = Message.conversation_id + ), + '[]' +) AS mentions, +TextContent.quoted_message_id AS quotedMessageId, +QuotedMessage.sender_user_id AS quotedSenderId, +TextContent.is_quote_verified AS isQuoteVerified, +QuotedSender.name AS quotedSenderName, +QuotedMessage.creation_date AS quotedMessageDateTime, +QuotedMessage.last_edit_date AS quotedMessageEditTimestamp, +QuotedMessage.visibility AS quotedMessageVisibility, +QuotedMessage.content_type AS quotedMessageContentType, +QuotedTextContent.text_body AS quotedTextBody, +QuotedAssetContent.asset_mime_type AS quotedAssetMimeType, +QuotedAssetContent.asset_name AS quotedAssetName, +QuotedLocationContent.name AS quotedLocationName, + +NewConversationReceiptMode.receipt_mode AS newConversationReceiptMode, + +ConversationReceiptModeChanged.receipt_mode AS conversationReceiptModeChanged, +ConversationTimerChangedContent.message_timer AS messageTimerChanged, +FailedRecipientsWithNoClients.recipient_failure_list AS recipientsFailedWithNoClientsList, +FailedRecipientsDeliveryFailed.recipient_failure_list AS recipientsFailedDeliveryList, + +IFNULL( + (SELECT '[' || + GROUP_CONCAT('{"text":"' || text || '", "id":"' || id || '""is_selected":' || is_selected || '}') + || ']' + FROM ButtonContent + WHERE ButtonContent.message_id = Message.id + AND ButtonContent.conversation_id = Message.conversation_id + ), + '[]' +) AS buttonsJson, +FederationTerminatedContent.domain_list AS federationDomainList, +FederationTerminatedContent.federation_type AS federationType, +ConversationProtocolChangedContent.protocol AS conversationProtocolChanged, +ConversationLocationContent.latitude AS latitude, +ConversationLocationContent.longitude AS longitude, +ConversationLocationContent.name AS locationName, +ConversationLocationContent.zoom AS locationZoom, +LegalHoldContent.legal_hold_member_list AS legalHoldMemberList, +LegalHoldContent.legal_hold_type AS legalHoldType + +FROM Message +JOIN UserDetails ON Message.sender_user_id = UserDetails.qualified_id +LEFT JOIN MessageTextContent AS TextContent ON Message.id = TextContent.message_id AND Message.conversation_id = TextContent.conversation_id +LEFT JOIN MessageAssetContent AS AssetContent ON Message.id = AssetContent.message_id AND Message.conversation_id = AssetContent.conversation_id +LEFT JOIN MessageMissedCallContent AS MissedCallContent ON Message.id = MissedCallContent.message_id AND Message.conversation_id = MissedCallContent.conversation_id +LEFT JOIN MessageMemberChangeContent AS MemberChangeContent ON Message.id = MemberChangeContent.message_id AND Message.conversation_id = MemberChangeContent.conversation_id +LEFT JOIN MessageUnknownContent AS UnknownContent ON Message.id = UnknownContent.message_id AND Message.conversation_id = UnknownContent.conversation_id +LEFT JOIN MessageRestrictedAssetContent AS RestrictedAssetContent ON Message.id = RestrictedAssetContent.message_id AND RestrictedAssetContent.conversation_id = RestrictedAssetContent.conversation_id +LEFT JOIN MessageFailedToDecryptContent AS FailedToDecryptContent ON Message.id = FailedToDecryptContent.message_id AND Message.conversation_id = FailedToDecryptContent.conversation_id +LEFT JOIN MessageConversationChangedContent AS ConversationNameChangedContent ON Message.id = ConversationNameChangedContent.message_id AND Message.conversation_id = ConversationNameChangedContent.conversation_id +LEFT JOIN MessageRecipientFailure AS FailedRecipientsWithNoClients ON Message.id = FailedRecipientsWithNoClients.message_id AND Message.conversation_id = FailedRecipientsWithNoClients.conversation_id AND FailedRecipientsWithNoClients.recipient_failure_type = 'NO_CLIENTS_TO_DELIVER' +LEFT JOIN MessageRecipientFailure AS FailedRecipientsDeliveryFailed ON Message.id = FailedRecipientsDeliveryFailed.message_id AND Message.conversation_id = FailedRecipientsDeliveryFailed.conversation_id AND FailedRecipientsDeliveryFailed.recipient_failure_type = 'MESSAGE_DELIVERY_FAILED' + +-- joins for quoted messages +LEFT JOIN Message AS QuotedMessage ON QuotedMessage.id = TextContent.quoted_message_id AND QuotedMessage.conversation_id = TextContent.conversation_id +LEFT JOIN User AS QuotedSender ON QuotedMessage.sender_user_id = QuotedSender.qualified_id +LEFT JOIN MessageTextContent AS QuotedTextContent ON QuotedTextContent.message_id = QuotedMessage.id AND QuotedMessage.conversation_id = TextContent.conversation_id +LEFT JOIN MessageAssetContent AS QuotedAssetContent ON QuotedAssetContent.message_id = QuotedMessage.id AND QuotedMessage.conversation_id = TextContent.conversation_id +LEFT JOIN MessageConversationLocationContent AS QuotedLocationContent ON QuotedLocationContent.message_id = QuotedMessage.id AND QuotedMessage.conversation_id = TextContent.conversation_id +-- end joins for quoted messages +LEFT JOIN MessageNewConversationReceiptModeContent AS NewConversationReceiptMode ON Message.id = NewConversationReceiptMode.message_id AND Message.conversation_id = NewConversationReceiptMode.conversation_id +LEFT JOIN MessageConversationReceiptModeChangedContent AS ConversationReceiptModeChanged ON Message.id = ConversationReceiptModeChanged.message_id AND Message.conversation_id = ConversationReceiptModeChanged.conversation_id +LEFT JOIN MessageConversationTimerChangedContent AS ConversationTimerChangedContent ON Message.id = ConversationTimerChangedContent.message_id AND Message.conversation_id = ConversationTimerChangedContent.conversation_id +LEFT JOIN MessageFederationTerminatedContent AS FederationTerminatedContent ON Message.id = FederationTerminatedContent.message_id AND Message.conversation_id = FederationTerminatedContent.conversation_id +LEFT JOIN MessageConversationProtocolChangedContent AS ConversationProtocolChangedContent ON Message.id = ConversationProtocolChangedContent.message_id AND Message.conversation_id = ConversationProtocolChangedContent.conversation_id +LEFT JOIN MessageConversationLocationContent AS ConversationLocationContent ON Message.id = ConversationLocationContent.message_id AND Message.conversation_id = ConversationLocationContent.conversation_id +LEFT JOIN MessageLegalHoldContent AS LegalHoldContent ON Message.id = LegalHoldContent.message_id AND Message.conversation_id = LegalHoldContent.conversation_id +LEFT JOIN SelfUser; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt index 5a89b8cc834..6199140d16c 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageEntity.kt @@ -308,6 +308,7 @@ sealed class MessageEntityContent { data class FailedDecryption( val encodedData: ByteArray? = null, + val code: Int?, val isDecryptionResolved: Boolean, val senderUserId: QualifiedIDEntity, val senderClientId: String?, diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt index 1f0566aa080..d98c426cf27 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageInsertExtension.kt @@ -193,6 +193,7 @@ internal class MessageInsertExtensionImpl( message_id = message.id, conversation_id = message.conversationId, unknown_encoded_data = content.encodedData, + error_code = content.code?.toLong() ) is MessageEntityContent.MemberChange -> messagesQueries.insertMemberChangeMessage( diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt index 399ae11c358..b07c3e05472 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageMapper.kt @@ -486,6 +486,7 @@ object MessageMapper { restrictedAssetSize: Long?, restrictedAssetName: String?, failedToDecryptData: ByteArray?, + decryptionErrorCode: Long?, isDecryptionResolved: Boolean?, conversationName: String?, allReactionsJson: String, @@ -576,6 +577,7 @@ object MessageMapper { MessageEntity.ContentType.FAILED_DECRYPTION -> MessageEntityContent.FailedDecryption( encodedData = failedToDecryptData, + code = decryptionErrorCode?.toInt(), isDecryptionResolved = isDecryptionResolved ?: false, senderUserId = senderUserId, senderClientId = senderClientId diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt index ae89c137b02..a638d8a63f4 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOTest.kt @@ -979,7 +979,7 @@ class MessageDAOTest : BaseDatabaseTest() { conversationId = conversationId, senderUserId = userEntity1.id, senderClientId = "someClient", - content = MessageEntityContent.FailedDecryption(null, false, userEntity1.id, "someClient") + content = MessageEntityContent.FailedDecryption(null, 333, false, userEntity1.id, "someClient") ), newRegularMessageEntity( id = messageId2, @@ -987,7 +987,7 @@ class MessageDAOTest : BaseDatabaseTest() { conversationId = conversationId2, senderUserId = userEntity1.id, senderClientId = "someClient", - content = MessageEntityContent.FailedDecryption(null, false, userEntity1.id, "someClient") + content = MessageEntityContent.FailedDecryption(null, 333, false, userEntity1.id, "someClient") ) ) ) @@ -1000,6 +1000,7 @@ class MessageDAOTest : BaseDatabaseTest() { assertTrue((updatedMessage?.content as MessageEntityContent.FailedDecryption).isDecryptionResolved) val updatedMessage2 = messageDAO.getMessageById(messageId2, conversationId2) assertTrue((updatedMessage2?.content as MessageEntityContent.FailedDecryption).isDecryptionResolved) + assertEquals(333, (updatedMessage2.content as MessageEntityContent.FailedDecryption).code) } @Test diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt index 9609b2152df..998bb343c09 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/message/MessageMapperTest.kt @@ -160,6 +160,7 @@ class MessageMapperTest { restrictedAssetSize: Long? = null, restrictedAssetName: String? = null, failedToDecryptData: ByteArray? = null, + decryptionErrorCode: Long? = null, isDecryptionResolved: Boolean? = null, conversationName: String? = null, allReactionsJson: String = "{}", @@ -251,6 +252,7 @@ class MessageMapperTest { restrictedAssetSize, restrictedAssetName, failedToDecryptData, + decryptionErrorCode, isDecryptionResolved, conversationName, allReactionsJson,