diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift index d8a560640..3ad45d698 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift @@ -165,6 +165,8 @@ struct CIRcvDecryptionError: View { message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why case .other: message = Text("\(msgCount) messages failed to decrypt.") + Text("\n") + why + case .ratchetSync: + message = Text("Encryption re-negotiation failed.") } return message } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index f8a6d78a5..551ed2794 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2676,6 +2676,7 @@ public enum MsgDecryptError: String, Decodable { case tooManySkipped case ratchetEarlier case other + case ratchetSync var text: String { switch self { @@ -2683,6 +2684,7 @@ public enum MsgDecryptError: String, Decodable { case .tooManySkipped: return NSLocalizedString("Permanent decryption error", comment: "message decrypt error item") case .ratchetEarlier: return NSLocalizedString("Decryption error", comment: "message decrypt error item") case .other: return NSLocalizedString("Decryption error", comment: "message decrypt error item") + case .ratchetSync: return NSLocalizedString("Encryption re-negotiation error", comment: "message decrypt error item") } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt index cf3fcbaae..eb6ed0bbf 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Share.android.kt @@ -35,7 +35,12 @@ actual fun shareFile(text: String, fileSource: CryptoFile) { val tmpFile = File(tmpDir, fileSource.filePath) tmpFile.deleteOnExit() ChatModel.filesToDelete.add(tmpFile) - decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath) + try { + decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + return + } getAppFileUri(tmpFile.absolutePath) } else { getAppFileUri(fileSource.filePath) @@ -96,15 +101,21 @@ fun saveImage(ciFile: CIFile?) { val outputStream = BufferedOutputStream(stream) if (ciFile.fileSource?.cryptoArgs != null) { createTmpFileAndDelete { tmpFile -> - decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath) + try { + decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + return@createTmpFileAndDelete + } tmpFile.inputStream().use { it.copyTo(outputStream) } + showToast(generalGetString(MR.strings.image_saved)) } outputStream.close() } else { File(filePath).inputStream().use { it.copyTo(outputStream) } outputStream.close() + showToast(generalGetString(MR.strings.image_saved)) } - showToast(generalGetString(MR.strings.image_saved)) } } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 91b4a8d8f..efd7ced3a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2106,13 +2106,15 @@ enum class MsgDecryptError { @SerialName("ratchetHeader") RatchetHeader, @SerialName("tooManySkipped") TooManySkipped, @SerialName("ratchetEarlier") RatchetEarlier, - @SerialName("other") Other; + @SerialName("other") Other, + @SerialName("ratchetSync") RatchetSync; val text: String get() = when (this) { RatchetHeader -> generalGetString(MR.strings.decryption_error) TooManySkipped -> generalGetString(MR.strings.decryption_error) RatchetEarlier -> generalGetString(MR.strings.decryption_error) Other -> generalGetString(MR.strings.decryption_error) + RatchetSync -> generalGetString(MR.strings.encryption_renegotiation_error) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 42e43f7b7..7d83693f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -894,7 +894,7 @@ fun BoxWithConstraintsScope.ChatItemsList( @Composable fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?) { - ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = { _, _ -> }, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools) + ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index 87f4aa4f3..57dcd16cb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -214,7 +214,13 @@ fun rememberSaveFileLauncher(ciFile: CIFile?): FileChooserLauncher = if (filePath != null && to != null) { if (ciFile?.fileSource?.cryptoArgs != null) { createTmpFileAndDelete { tmpFile -> - decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath) + try { + decryptCryptoFile(filePath, ciFile.fileSource.cryptoArgs, tmpFile.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + tmpFile.delete() + return@createTmpFileAndDelete + } copyFileToFile(tmpFile, to) {} tmpFile.delete() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt index ecf7f10dd..318735d73 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt @@ -218,5 +218,7 @@ private fun alertMessage(msgDecryptError: MsgDecryptError, msgCount: UInt): Stri MsgDecryptError.Other -> String.format(generalGetString(MR.strings.alert_text_decryption_error_n_messages_failed_to_decrypt), msgCount.toLong()) + "\n" + generalGetString(MR.strings.alert_text_fragment_encryption_out_of_sync_old_database) + + MsgDecryptError.RatchetSync -> generalGetString(MR.strings.alert_text_encryption_renegotiation_failed) } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 170a28f3d..d2f464297 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -45,6 +45,7 @@ invalid chat invalid data Decryption error + Encryption re-negotiation error connection %1$d @@ -866,6 +867,7 @@ %1$d messages failed to decrypt. %1$d messages skipped. It can happen when you or your connection used the old database backup. + Encryption re-negotiation failed. Please report it to the developers. diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt index 25fc9ec8d..83351d772 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt @@ -59,6 +59,7 @@ actual object AudioPlayer: AudioPlayerInterface { } }.onFailure { Log.e(TAG, it.stackTraceToString()) + fileSource.deleteTmpFile() AlertManager.shared.showAlertMsg(generalGetString(MR.strings.unknown_error), it.message) return null } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt index 1d5ab45bb..a91bc5a76 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt @@ -27,7 +27,11 @@ actual fun shareFile(text: String, fileSource: CryptoFile) { FileChooserLauncher(false) { to: URI? -> if (to != null) { if (fileSource.cryptoArgs != null) { - decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, to.path) + try { + decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, to.path) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + } } else { copyFileToFile(File(fileSource.filePath), to) {} } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt index 9df5bd0a1..f602dd577 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt @@ -48,7 +48,12 @@ actual fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) { val filePath: String = if (fileSource.cryptoArgs != null) { val tmpFile = File(tmpDir, fileSource.filePath) tmpFile.deleteOnExit() - decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath) + try { + decryptCryptoFile(getAppFilePath(fileSource.filePath), fileSource.cryptoArgs, tmpFile.absolutePath) + } catch (e: Exception) { + Log.e(TAG, "Unable to decrypt crypto file: " + e.stackTraceToString()) + return + } tmpFile.absolutePath } else { getAppFilePath(fileSource.filePath) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt index e867dd1b3..7478e22a4 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -94,9 +94,14 @@ actual fun getAppFileUri(fileName: String): URI = actual fun getLoadedImage(file: CIFile?): Pair? { val filePath = getLoadedFilePath(file) return if (filePath != null) { - val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes() - val bitmap = getBitmapFromByteArray(data, false) - if (bitmap != null) bitmap to data else null + try { + val data = if (file?.fileSource?.cryptoArgs != null) readCryptoFile(filePath, file.fileSource.cryptoArgs) else File(filePath).readBytes() + val bitmap = getBitmapFromByteArray(data, false) + if (bitmap != null) bitmap to data else null + } catch (e: Exception) { + Log.e(TAG, "Unable to read crypto file: " + e.stackTraceToString()) + null + } } else { null } diff --git a/cabal.project b/cabal.project index 84231a6b8..296631018 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 897001efbf0155f2954f6e051b08e12ceccb9a15 + tag: 20d9767c5474de083b711cc034c871af3b57f6f7 source-repository-package type: git diff --git a/docs/DOWNLOADS.md b/docs/DOWNLOADS.md index 94b8d9197..a1576c355 100644 --- a/docs/DOWNLOADS.md +++ b/docs/DOWNLOADS.md @@ -25,7 +25,7 @@ Using the same profile as on mobile device is not yet supported – you need to **Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.2/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon). -**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.0/simplex-desktop-windows-x86-64.msi) (BETA). +**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.3/simplex-desktop-windows-x86-64.msi) (BETA). ## Mobile apps diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 8be4e0a3d..2d5562ad8 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."897001efbf0155f2954f6e051b08e12ceccb9a15" = "0spjv7yqdd49kxh5vf14n7wnvyas37nclwigxq705cx2radjs9z9"; + "https://github.com/simplex-chat/simplexmq.git"."20d9767c5474de083b711cc034c871af3b57f6f7" = "13lyrd9q3qa1b2sfar1gbwxx9bmwramqqry7zj5pnr2ll2xg67s2"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6"; "https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index a70c2870f..6fed6c3db 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1248,7 +1248,7 @@ processChatCommand = \case connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId pure $ CRGroupMemberSwitchAborted user g m connectionStats _ -> throwChatError CEGroupMemberNotActive - APISyncContactRatchet contactId force -> withUser $ \user -> do + APISyncContactRatchet contactId force -> withUser $ \user -> withChatLock "syncContactRatchet" $ do ct <- withStore $ \db -> getContact db user contactId case contactConnId ct of Just connId -> do @@ -1256,7 +1256,7 @@ processChatCommand = \case createInternalChatItem user (CDDirectSnd ct) (CISndConnEvent $ SCERatchetSync rss Nothing) Nothing pure $ CRContactRatchetSyncStarted user ct cStats Nothing -> throwChatError $ CEContactNotActive ct - APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> do + APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> withChatLock "syncGroupMemberRatchet" $ do (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db user gId <*> getGroupMember db user gId gMemberId case memberConnId m of Just connId -> do @@ -3627,6 +3627,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do RATCHET_HEADER -> (MDERatchetHeader, 1) RATCHET_EARLIER _ -> (MDERatchetEarlier, 1) RATCHET_SKIPPED n -> (MDETooManySkipped, n) + RATCHET_SYNC -> (MDERatchetSync, 0) mdeUpdatedCI :: (MsgDecryptError, Word32) -> CChatItem c -> Maybe (ChatItem c 'MDRcv, CIContent 'MDRcv) mdeUpdatedCI (mde', n') (CChatItem _ ci@ChatItem {content = CIRcvDecryptionError mde n}) @@ -3635,6 +3636,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do MDETooManySkipped -> r n' -- the numbers are not added as sequential MDETooManySkipped will have it incremented by 1 MDERatchetEarlier -> r (n + n') MDEOther -> r (n + n') + MDERatchetSync -> r 0 | otherwise = Nothing where r n'' = Just (ci, CIRcvDecryptionError mde n'') diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 639093d01..ea5c7dfe0 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -150,7 +150,12 @@ ciMsgContent = \case CIRcvMsgContent mc -> Just mc _ -> Nothing -data MsgDecryptError = MDERatchetHeader | MDETooManySkipped | MDERatchetEarlier | MDEOther +data MsgDecryptError + = MDERatchetHeader + | MDETooManySkipped + | MDERatchetEarlier + | MDEOther + | MDERatchetSync deriving (Eq, Show, Generic) instance ToJSON MsgDecryptError where @@ -460,6 +465,7 @@ msgDecryptErrorText err n = MDETooManySkipped -> Just $ "too many skipped messages" <> counter MDERatchetEarlier -> Just $ "earlier message" <> counter MDEOther -> counter_ + MDERatchetSync -> Just "synchronization error" counter_ = if n == 1 then Nothing else Just $ tshow n <> " messages" counter = maybe "" (", " <>) counter_ @@ -555,7 +561,7 @@ jsonCIContent = \case CIRcvChatFeatureRejected feature -> JCIRcvChatFeatureRejected {feature} CIRcvGroupFeatureRejected groupFeature -> JCIRcvGroupFeatureRejected {groupFeature} CISndModerated -> JCISndModerated - CIRcvModerated -> JCISndModerated + CIRcvModerated -> JCIRcvModerated CIInvalidJSON json -> JCIInvalidJSON (toMsgDirection $ msgDirection @d) json aciContentJSON :: JSONCIContent -> ACIContent diff --git a/stack.yaml b/stack.yaml index 6854a9922..4e2503a3b 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: 897001efbf0155f2954f6e051b08e12ceccb9a15 + commit: 20d9767c5474de083b711cc034c871af3b57f6f7 - github: kazu-yamamoto/http2 commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb # - ../direct-sqlcipher