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