diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 6658c7c3..d53af97b 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -14,7 +14,7 @@ steps: - "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace" artifact_paths: - "vector/build/outputs/apk/gplay/debug/*.apk" - branches: "develop feature/*" + branches: "!master" plugins: - docker#v3.1.0: image: "runmymind/docker-android-sdk" @@ -28,7 +28,7 @@ steps: - "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace" artifact_paths: - "vector/build/outputs/apk/fdroid/debug/*.apk" - branches: "develop feature/*" + branches: "!master" plugins: - docker#v3.1.0: image: "runmymind/docker-android-sdk" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt index eaaeb730..5cb7f4ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixPatterns.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.api -import java.util.regex.Pattern /** * This class contains pattern to match the different Matrix ids @@ -29,31 +28,31 @@ object MatrixPatterns { // regex pattern to find matrix user ids in a string. // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" - private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find room ids in a string. private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" - private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find room aliases in a string. private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX" - private val PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find message ids in a string. private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find message ids in a string. private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE) // Ref: https://matrix.org/docs/spec/rooms/v4#event-ids private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+" - private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V4_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find group ids in a string. private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX" - private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find permalink with message id. // Android does not support in URL so extract it. @@ -62,16 +61,16 @@ object MatrixPatterns { const val SEP_REGEX = "/" private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX - private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE) private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX - private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX - private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE) private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX - private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE) + private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) // list of patterns to find some matrix item. val MATRIX_PATTERNS = listOf( @@ -93,7 +92,7 @@ object MatrixPatterns { * @return true if the string is a valid user id */ fun isUserId(str: String?): Boolean { - return str != null && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches() + return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER } /** @@ -103,7 +102,7 @@ object MatrixPatterns { * @return true if the string is a valid room Id */ fun isRoomId(str: String?): Boolean { - return str != null && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches() + return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER } /** @@ -113,7 +112,7 @@ object MatrixPatterns { * @return true if the string is a valid room alias. */ fun isRoomAlias(str: String?): Boolean { - return str != null && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches() + return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS } /** @@ -124,9 +123,9 @@ object MatrixPatterns { */ fun isEventId(str: String?): Boolean { return str != null - && (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() - || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches() - || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches()) + && (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER + || str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 + || str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) } /** @@ -136,6 +135,6 @@ object MatrixPatterns { * @return true if the string is a valid group id. */ fun isGroupId(str: String?): Boolean { - return str != null && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches() + return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 593c33b9..152adb06 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -38,7 +38,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString())) - data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString())) + data class CryptoError(val error: MXCryptoError) : Failure(error) abstract class FeatureFailure : Failure() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index eed9b7b6..cfe5a051 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -37,15 +37,13 @@ object MatrixLinkify { } val text = spannable.toString() var hasMatch = false - for (index in MatrixPatterns.MATRIX_PATTERNS.indices) { - val pattern = MatrixPatterns.MATRIX_PATTERNS[index] - val matcher = pattern.matcher(spannable) - while (matcher.find()) { + for (pattern in MatrixPatterns.MATRIX_PATTERNS) { + for (match in pattern.findAll(spannable)) { hasMatch = true - val startPos = matcher.start(0) + val startPos = match.range.first if (startPos == 0 || text[startPos - 1] != '/') { - val endPos = matcher.end(0) - val url = text.substring(matcher.start(0), matcher.end(0)) + val endPos = match.range.last + val url = text.substring(match.range) val span = MatrixPermalinkSpan(url, callback) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index a3ece1ab..0397b514 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -98,9 +98,10 @@ interface CryptoService { roomId: String, callback: MatrixCallback) - fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? + @Throws(MXCryptoError::class) + fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult - fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) + fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) fun getEncryptionAlgorithm(roomId: String): String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/MXCryptoError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/MXCryptoError.kt index 1e01350c..42102124 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/MXCryptoError.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/MXCryptoError.kt @@ -18,108 +18,58 @@ package im.vector.matrix.android.api.session.crypto -import android.text.TextUtils +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import org.matrix.olm.OlmException /** * Represents a crypto error response. */ -class MXCryptoError(var code: String, - var message: String) { +sealed class MXCryptoError : Throwable() { - /** - * Describe the error with more details - */ - private var mDetailedErrorDescription: String? = null + data class Base(val errorType: ErrorType, + val technicalMessage: String, + /** + * Describe the error with more details + */ + val detailedErrorDescription: String? = null) : MXCryptoError() - /** - * Data exception. - * Some exceptions provide some data to describe the exception - */ - var mExceptionData: Any? = null + data class OlmError(val olmException: OlmException) : MXCryptoError() - /** - * @return true if the current error is an olm one. - */ - val isOlmError: Boolean - get() = OLM_ERROR_CODE == code + data class UnknownDevice(val deviceList: MXUsersDevicesMap) : MXCryptoError() - - /** - * @return the detailed error description - */ - val detailedErrorDescription: String? - get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) { - message - } else mDetailedErrorDescription - - /** - * Create a crypto error - * - * @param code the error code (see XX_ERROR_CODE) - * @param shortErrorDescription the short error description - * @param detailedErrorDescription the detailed error description - */ - constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) { - mDetailedErrorDescription = detailedErrorDescription - } - - /** - * Create a crypto error - * - * @param code the error code (see XX_ERROR_CODE) - * @param shortErrorDescription the short error description - * @param detailedErrorDescription the detailed error description - * @param exceptionData the exception data - */ - constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?, exceptionData: Any) : this(code, shortErrorDescription) { - mDetailedErrorDescription = detailedErrorDescription - mExceptionData = exceptionData + enum class ErrorType { + ENCRYPTING_NOT_ENABLED, + UNABLE_TO_ENCRYPT, + UNABLE_TO_DECRYPT, + UNKNOWN_INBOUND_SESSION_ID, + INBOUND_SESSION_MISMATCH_ROOM_ID, + MISSING_FIELDS, + BAD_EVENT_FORMAT, + MISSING_SENDER_KEY, + MISSING_CIPHER_TEXT, + BAD_DECRYPTED_FORMAT, + NOT_INCLUDE_IN_RECIPIENTS, + BAD_RECIPIENT, + BAD_RECIPIENT_KEY, + FORWARDED_MESSAGE, + BAD_ROOM, + BAD_ENCRYPTED_MESSAGE, + DUPLICATED_MESSAGE_INDEX, + MISSING_PROPERTY, + OLM, + UNKNOWN_DEVICES, + UNKNOWN_MESSAGE_INDEX } companion object { - - // TODO Create sealed class - /** - * Error codes + * Resource for technicalMessage */ - const val UNKNOWN_ERROR_CODE = "UNKNOWN_ERROR_CODE" - const val ENCRYPTING_NOT_ENABLED_ERROR_CODE = "ENCRYPTING_NOT_ENABLED" - const val UNABLE_TO_ENCRYPT_ERROR_CODE = "UNABLE_TO_ENCRYPT" - const val UNABLE_TO_DECRYPT_ERROR_CODE = "UNABLE_TO_DECRYPT" - const val UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE = "UNKNOWN_INBOUND_SESSION_ID" - const val INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE = "INBOUND_SESSION_MISMATCH_ROOM_ID" - const val MISSING_FIELDS_ERROR_CODE = "MISSING_FIELDS" - const val BAD_EVENT_FORMAT_ERROR_CODE = "BAD_EVENT_FORMAT_ERROR_CODE" - const val MISSING_SENDER_KEY_ERROR_CODE = "MISSING_SENDER_KEY_ERROR_CODE" - const val MISSING_CIPHER_TEXT_ERROR_CODE = "MISSING_CIPHER_TEXT" - const val BAD_DECRYPTED_FORMAT_ERROR_CODE = "BAD_DECRYPTED_FORMAT_ERROR_CODE" - const val NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE = "NOT_INCLUDE_IN_RECIPIENTS" - const val BAD_RECIPIENT_ERROR_CODE = "BAD_RECIPIENT" - const val BAD_RECIPIENT_KEY_ERROR_CODE = "BAD_RECIPIENT_KEY" - const val FORWARDED_MESSAGE_ERROR_CODE = "FORWARDED_MESSAGE" - const val BAD_ROOM_ERROR_CODE = "BAD_ROOM" - const val BAD_ENCRYPTED_MESSAGE_ERROR_CODE = "BAD_ENCRYPTED_MESSAGE" - const val DUPLICATED_MESSAGE_INDEX_ERROR_CODE = "DUPLICATED_MESSAGE_INDEX" - const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY" - const val OLM_ERROR_CODE = "OLM_ERROR_CODE" - const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE" - const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX" - - /** - * short error reasons - */ - const val UNABLE_TO_DECRYPT = "Unable to decrypt" - const val UNABLE_TO_ENCRYPT = "Unable to encrypt" - - /** - * Detailed error reasons - */ - const val ENCRYPTING_NOT_ENABLED_REASON = "Encryption not enabled" const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s" const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s" const val OLM_REASON = "OLM error: %1\$s" - const val DETAILLED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s" + const val DETAILED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s" const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id" const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)" const val MISSING_FIELDS_REASON = "Missing fields in input" @@ -138,4 +88,4 @@ class MXCryptoError(var code: String, const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing." const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event." } -} +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index 26587359..f02b7d31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -140,37 +140,32 @@ data class Event( * * @param decryptionResult the decryption result, including the plaintext and some key info. */ - internal fun setClearData(decryptionResult: MXEventDecryptionResult?) { + internal fun setClearData(decryptionResult: MXEventDecryptionResult) { mClearEvent = null - if (decryptionResult != null) { - if (decryptionResult.clearEvent != null) { - val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) - mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) + mCryptoError = null - if (mClearEvent != null) { - mSenderCurve25519Key = decryptionResult.senderCurve25519Key - mClaimedEd25519Key = decryptionResult.claimedEd25519Key - mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain + val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java) + mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent) - // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back - // in the clear event - try { - content?.get("m.relates_to")?.let { clearRelates -> - mClearEvent = mClearEvent?.copy( - content = HashMap(mClearEvent!!.content).apply { - this["m.relates_to"] = clearRelates - } - ) - } - } catch (e: Exception) { - Timber.e(e, "Unable to restore 'm.relates_to' the clear event") - } + if (mClearEvent != null) { + mSenderCurve25519Key = decryptionResult.senderCurve25519Key + mClaimedEd25519Key = decryptionResult.claimedEd25519Key + mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain + + // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back + // in the clear event + try { + content?.get("m.relates_to")?.let { clearRelates -> + mClearEvent = mClearEvent?.copy( + content = HashMap(mClearEvent!!.content).apply { + this["m.relates_to"] = clearRelates + } + ) } - - + } catch (e: Exception) { + Timber.e(e, "Unable to restore 'm.relates_to' the clear event") } } - mCryptoError = null } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/CancelableBag.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/CancelableBag.kt index 95fdc3de..f5689b4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/CancelableBag.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/CancelableBag.kt @@ -16,20 +16,8 @@ package im.vector.matrix.android.api.util -class CancelableBag : Cancelable { - - private val cancelableList = ArrayList() - - fun add(cancelable: Cancelable) { - cancelableList.add(cancelable) - } - +class CancelableBag : Cancelable, MutableList by ArrayList() { override fun cancel() { - cancelableList.forEach { it.cancel() } + forEach { it.cancel() } } - } - -fun Cancelable.addTo(cancelables: CancelableBag) { - cancelables.add(this) -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixCallbackDelegate.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixCallbackDelegate.kt index f98cf5a3..2b4e30a1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixCallbackDelegate.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixCallbackDelegate.kt @@ -21,13 +21,4 @@ import im.vector.matrix.android.api.MatrixCallback /** * Simple MatrixCallback implementation which delegate its calls to another callback */ -open class MatrixCallbackDelegate(private val callback: MatrixCallback) : MatrixCallback { - - override fun onSuccess(data: T) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } -} \ No newline at end of file +open class MatrixCallbackDelegate(private val callback: MatrixCallback) : MatrixCallback by callback \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 154bfbc9..667f1446 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -569,8 +569,7 @@ internal class CryptoManager @Inject constructor( val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) Timber.e("## encryptEventContent() : $reason") - callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, reason))) + callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))) } } } @@ -580,10 +579,10 @@ internal class CryptoManager @Inject constructor( * * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @return the MXEventDecryptionResult data, or null in case of error + * @return the MXEventDecryptionResult data, or throw in case of error */ - @Throws(MXDecryptionException::class) - override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? { + @Throws(MXCryptoError::class) + override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { return runBlocking { internalDecryptEvent(event, timeline).fold( { throw it }, @@ -599,7 +598,7 @@ internal class CryptoManager @Inject constructor( * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param callback the callback to return data or null */ - override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { + override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { GlobalScope.launch(EmptyCoroutineContext) { val result = withContext(coroutineDispatchers.crypto) { internalDecryptEvent(event, timeline) @@ -616,18 +615,17 @@ internal class CryptoManager @Inject constructor( * @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try] */ private suspend fun internalDecryptEvent(event: Event, timeline: String): Try { - return Try { - val eventContent = event.content - if (eventContent == null) { - Timber.e("## decryptEvent : empty event content") - throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE)) - } + val eventContent = event.content + return if (eventContent == null) { + Timber.e("## decryptEvent : empty event content") + Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) + } else { val algorithm = eventContent["algorithm"]?.toString() val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) if (alg == null) { val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) Timber.e("## decryptEvent() : $reason") - throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason)) + Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)) } else { alg.decryptEvent(event, timeline) } @@ -872,7 +870,7 @@ internal class CryptoManager @Inject constructor( /** * Check if the user ids list have some unknown devices. * A success means there is no unknown devices. - * If there are some unknown devices, a MXCryptoError.UNKNOWN_DEVICES_CODE exception is triggered. + * If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered. * * @param userIds the user ids list * @param callback the asynchronous callback. @@ -890,9 +888,7 @@ internal class CryptoManager @Inject constructor( callback.onSuccess(Unit) } else { // trigger an an unknown devices exception - callback.onFailure( - Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) + callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices))) } } ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXDecryptionException.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXDecryptionException.kt deleted file mode 100644 index b0d91c21..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXDecryptionException.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2016 OpenMarket Ltd - * Copyright 2017 Vector Creations Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.crypto - -import im.vector.matrix.android.api.session.crypto.MXCryptoError - -/** - * This class represents a decryption exception - */ -class MXDecryptionException -( - /** - * the linked crypto error - */ - val cryptoError: MXCryptoError? -) : Exception() { - - override val message: String? - get() = cryptoError?.message ?: super.message - - override fun getLocalizedMessage(): String { - return cryptoError?.message ?: super.getLocalizedMessage() - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt old mode 100644 new mode 100755 index a0c44382..332ca860 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt @@ -1,6 +1,5 @@ /* * Copyright 2016 OpenMarket Ltd - * Copyright 2017 Vector Creations Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +17,6 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.util.JsonDict -import java.util.* /** * The result of a (successful) call to decryptEvent. @@ -28,23 +26,23 @@ data class MXEventDecryptionResult( /** * The plaintext payload for the event (typically containing "type" and "content" fields). */ - var clearEvent: JsonDict? = null, + val clearEvent: JsonDict, /** * Key owned by the sender of this event. * See MXEvent.senderKey. */ - var senderCurve25519Key: String? = null, + val senderCurve25519Key: String? = null, /** * Ed25519 key claimed by the sender of this event. * See MXEvent.claimedEd25519Key. */ - var claimedEd25519Key: String? = null, + val claimedEd25519Key: String? = null, /** * List of curve25519 keys involved in telling us about the senderCurve25519Key and * claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain. */ - var forwardingCurve25519KeyChain: List = ArrayList() + val forwardingCurve25519KeyChain: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt index 7fc9cc51..a9ea4a73 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt @@ -18,10 +18,11 @@ package im.vector.matrix.android.internal.crypto import android.text.TextUtils +import arrow.core.Try import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JsonDict -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -81,11 +82,6 @@ internal class MXOlmDevice @Inject constructor( // Values are true. private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap() - /** - * inboundGroupSessionWithId error - */ - private var inboundGroupSessionWithIdError: MXCryptoError? = null - init { // Retrieve the account from the store olmAccount = store.getAccount() @@ -508,22 +504,26 @@ internal class MXOlmDevice @Inject constructor( forwardingCurve25519KeyChain: List, keysClaimed: Map, exportFormat: Boolean): Boolean { - val existingInboundSession = getInboundGroupSession(sessionId, senderKey, roomId) val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat) - if (null != existingInboundSession) { - // If we already have this session, consider updating it - Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId") + getInboundGroupSession(sessionId, senderKey, roomId).fold( + { + // Nothing to do in case of error + }, + { + // If we already have this session, consider updating it + Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - val existingFirstKnown = existingInboundSession.firstKnownIndex!! - val newKnownFirstIndex = session.firstKnownIndex!! + val existingFirstKnown = it.firstKnownIndex!! + val newKnownFirstIndex = session.firstKnownIndex - //If our existing session is better we keep it - if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { - session.olmInboundGroupSession?.releaseSession() - return false - } - } + //If our existing session is better we keep it + if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { + session.olmInboundGroupSession?.releaseSession() + return false + } + } + ) // sanity check if (null == session.olmInboundGroupSession) { @@ -594,20 +594,26 @@ internal class MXOlmDevice @Inject constructor( continue } - val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId) - if (null != existingOlmSession) { - // If we already have this session, consider updating it - Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") + getInboundGroupSession(sessionId, senderKey, roomId) + .fold( + { + // Session does not already exist, add it + sessions.add(session) + }, + { + // If we already have this session, consider updating it + Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") - // For now we just ignore updates. TODO: implement something here - if (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) { - //Ignore this, keep existing - session.olmInboundGroupSession!!.releaseSession() - continue - } - } - - sessions.add(session) + // For now we just ignore updates. TODO: implement something here + if (it.firstKnownIndex!! <= session.firstKnownIndex!!) { + //Ignore this, keep existing + session.olmInboundGroupSession!!.releaseSession() + } else { + sessions.add(session) + } + Unit + } + ) } store.storeInboundGroupSessions(sessions) @@ -637,81 +643,64 @@ internal class MXOlmDevice @Inject constructor( * @param senderKey the base64-encoded curve25519 key of the sender. * @return the decrypting result. Nil if the sessionId is unknown. */ - @Throws(MXDecryptionException::class) fun decryptGroupMessage(body: String, roomId: String, timeline: String?, sessionId: String, - senderKey: String): MXDecryptionResult? { - val result = MXDecryptionResult() - val session = getInboundGroupSession(sessionId, senderKey, roomId) - - if (null != session) { - // Check that the room id matches the original one for the session. This stops - // the HS pretending a message was targeting a different room. - if (TextUtils.equals(roomId, session.roomId)) { - var errorMessage = "" - var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null - try { - decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) - } catch (e: Exception) { - Timber.e(e, "## decryptGroupMessage () : decryptMessage failed") - errorMessage = e.message ?: "" - } - - if (null != decryptResult) { - if (null != timeline) { - if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) { - inboundGroupSessionMessageIndexes[timeline] = HashMap() + senderKey: String): Try { + return getInboundGroupSession(sessionId, senderKey, roomId) + .flatMap { session -> + // Check that the room id matches the original one for the session. This stops + // the HS pretending a message was targeting a different room. + if (roomId == session.roomId) { + var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null + try { + decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) + } catch (e: OlmException) { + Timber.e(e, "## decryptGroupMessage () : decryptMessage failed") + return@flatMap Try.Failure(MXCryptoError.OlmError(e)) } - val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex + if (null != timeline) { + if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) { + inboundGroupSessionMessageIndexes[timeline] = HashMap() + } - if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) { - val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) - Timber.e("## decryptGroupMessage() : $reason") - throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, reason)) + val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex + + if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) { + val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) + Timber.e("## decryptGroupMessage() : $reason") + return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)) + } + + inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true) } - inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true) - } + store.storeInboundGroupSessions(listOf(session)) + val payload = try { + val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) + val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) + adapter.fromJson(payloadString) + } catch (e: Exception) { + Timber.e("## decryptGroupMessage() : fails to parse the payload") + return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)) + } - store.storeInboundGroupSessions(listOf(session)) - try { - val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) - val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage) - val payload = adapter.fromJson(payloadString) - result.payload = payload - } catch (e: Exception) { - Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message) - return null + return@flatMap Try.just( + OlmDecryptionResult( + payload, + session.keysClaimed, + senderKey, + session.forwardingCurve25519KeyChain + ) + ) + } else { + val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) + Timber.e("## decryptGroupMessage() : $reason") + return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)) } - - if (null == result.payload) { - Timber.e("## decryptGroupMessage() : fails to parse the payload") - return null - } - - result.keysClaimed = session.keysClaimed - result.senderKey = senderKey - result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain - } else { - Timber.e("## decryptGroupMessage() : failed to decode the message") - throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null)) } - } else { - val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) - Timber.e("## decryptGroupMessage() : $reason") - throw MXDecryptionException(MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, reason)) - } - } else { - Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId") - throw MXDecryptionException(inboundGroupSessionWithIdError) - } - - return result } /** @@ -725,7 +714,7 @@ internal class MXOlmDevice @Inject constructor( } } - // Utilities +// Utilities /** * Verify an ed25519 signature on a JSON object. @@ -775,27 +764,27 @@ internal class MXOlmDevice @Inject constructor( * @param senderKey the base64-encoded curve25519 key of the sender. * @return the inbound group session. */ - fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper? { - inboundGroupSessionWithIdError = null + fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try { + if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) { + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)) + } - if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) return null val session = store.getInboundGroupSession(sessionId, senderKey) - if (null != session) { + return if (null != session) { // Check that the room id matches the original one for the session. This stops // the HS pretending a message was targeting a different room. if (!TextUtils.equals(roomId, session.roomId)) { val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) Timber.e("## getInboundGroupSession() : $errorDescription") - inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, errorDescription) + Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)) + } else { + Try.just(session) } } else { Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") - inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE, - MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null) + Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)) } - return session } /** @@ -807,6 +796,6 @@ internal class MXOlmDevice @Inject constructor( * @return true if the unbound session keys are known. */ fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { - return null != getInboundGroupSession(sessionId, senderKey, roomId) + return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt index c21ccbdb..8714e156 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt @@ -17,9 +17,9 @@ package im.vector.matrix.android.internal.crypto.algorithms +import arrow.core.Try import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.MXDecryptionException import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup @@ -33,11 +33,9 @@ internal interface IMXDecrypting { * * @param event the raw event. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. - * @return the decryption information, or null in case of error - * @throws MXDecryptionException the decryption failure reason + * @return the decryption information, or an error */ - @Throws(MXDecryptionException::class) - suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult + suspend fun decryptEvent(event: Event, timeline: String): Try /** * Handle a key event. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt index 5e9d305e..544bbe60 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXEncrypting.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.algorithms import arrow.core.Try -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Content /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 42418c0a..fb90db7d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -28,7 +28,6 @@ import im.vector.matrix.android.internal.crypto.* import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting -import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap @@ -64,66 +63,71 @@ internal class MXMegolmDecryption(private val credentials: Credentials, */ private var pendingEvents: MutableMap>> = HashMap() - @Throws(MXDecryptionException::class) - override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { + override suspend fun decryptEvent(event: Event, timeline: String): Try { return decryptEvent(event, timeline, true) } - @Throws(MXDecryptionException::class) - private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult { - val encryptedEventContent = event.content.toModel()!! - if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) { - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON)) + private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try { + if (event.roomId.isNullOrBlank()) { + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) } - var cryptoError: MXCryptoError? = null - var decryptGroupMessageResult: MXDecryptionResult? = null + val encryptedEventContent = event.content.toModel() + ?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) - try { - decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!) - } catch (e: MXDecryptionException) { - cryptoError = e.cryptoError + if (encryptedEventContent.senderKey.isNullOrBlank() + || encryptedEventContent.sessionId.isNullOrBlank() + || encryptedEventContent.ciphertext.isNullOrBlank()) { + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) } - // the decryption succeeds - if (decryptGroupMessageResult?.payload != null) { - val eventDecryptionResult = MXEventDecryptionResult() - eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload - eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey + return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, event.roomId, timeline, encryptedEventContent.sessionId, encryptedEventContent.senderKey) + .fold( + { throwable -> + if (throwable is MXCryptoError.OlmError) { + // TODO Check the value of .message + if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") { + addEventToPendingList(event, timeline) + if (requestKeysOnFail) { + requestKeysForEvent(event) + } + } - if (null != decryptGroupMessageResult.keysClaimed) { - eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed?.get("ed25519") - } + val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message) + val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason) - eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain - ?: emptyList() - return eventDecryptionResult - } else if (cryptoError != null) { - if (cryptoError.isOlmError) { - if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) { - addEventToPendingList(event, timeline) - if (requestKeysOnFail) { - requestKeysForEvent(event) - } - } + Try.Failure(MXCryptoError.Base( + MXCryptoError.ErrorType.OLM, + reason, + detailedReason)) + } + if (throwable is MXCryptoError.Base) { + if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { + addEventToPendingList(event, timeline) + if (requestKeysOnFail) { + requestKeysForEvent(event) + } + } + } - val reason = String.format(MXCryptoError.OLM_REASON, cryptoError.message) - val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message) - - throw MXDecryptionException(MXCryptoError( - MXCryptoError.OLM_ERROR_CODE, - reason, - detailedReason)) - } else if (TextUtils.equals(cryptoError.code, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) { - addEventToPendingList(event, timeline) - if (requestKeysOnFail) { - requestKeysForEvent(event) - } - throw MXDecryptionException(cryptoError) - } - } - throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE)) + Try.Failure(throwable) + }, + { olmDecryptionResult -> + // the decryption succeeds + if (olmDecryptionResult.payload != null) { + Try.just( + MXEventDecryptionResult( + clearEvent = olmDecryptionResult.payload, + senderCurve25519Key = olmDecryptionResult.senderKey, + claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"), + forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList() + ) + ) + } else { + Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) + } + } + ) } @@ -319,11 +323,20 @@ internal class MXMegolmDecryption(private val credentials: Credentials, Try.just(Unit) } Timber.v("""## shareKeysWithDevice() : sharing keys for session ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId""") - val inboundGroupSession = olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) val payloadJson = HashMap() payloadJson["type"] = EventType.FORWARDED_ROOM_KEY - payloadJson["content"] = inboundGroupSession!!.exportKeys()!! + + olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) + .fold( + { + // TODO + }, + { + // TODO + payloadJson["content"] = it.exportKeys() ?: "" + } + ) val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo)) val sendToDeviceMap = MXUsersDevicesMap() @@ -332,8 +345,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials, val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) sendToDeviceTask.execute(sendToDeviceParams) } - - } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index c816cbb6..ca9a105e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm import android.text.TextUtils import arrow.core.Try import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.EventType @@ -254,29 +253,31 @@ internal class MXMegolmEncryption( /** * process the pending encryptions */ - private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try { - // Everything is in place, encrypt all pending events - val payloadJson = HashMap() - payloadJson["room_id"] = roomId - payloadJson["type"] = eventType - payloadJson["content"] = eventContent + private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try { + return Try { + // Everything is in place, encrypt all pending events + val payloadJson = HashMap() + payloadJson["room_id"] = roomId + payloadJson["type"] = eventType + payloadJson["content"] = eventContent - // Get canonical Json from + // Get canonical Json from - val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson)) - val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) + val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson)) + val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) - val map = HashMap() - map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM - map["sender_key"] = olmDevice.deviceCurve25519Key!! - map["ciphertext"] = ciphertext!! - map["session_id"] = session.sessionId + val map = HashMap() + map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM + map["sender_key"] = olmDevice.deviceCurve25519Key!! + map["ciphertext"] = ciphertext!! + map["session_id"] = session.sessionId - // Include our device ID so that recipients can send us a - // m.new_device message if they don't have our session key. - map["device_id"] = credentials.deviceId!! - session.useCount++ - map + // Include our device ID so that recipients can send us a + // m.new_device message if they don't have our session key. + map["device_id"] = credentials.deviceId!! + session.useCount++ + map + } } /** @@ -328,9 +329,7 @@ internal class MXMegolmEncryption( if (unknownDevices.isEmpty) { Try.just(devicesInRoom) } else { - val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices) - Try.Failure(Failure.CryptoError(cryptoError)) + Try.Failure(MXCryptoError.UnknownDevice(unknownDevices)) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 2714784c..e24132c3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -17,13 +17,13 @@ package im.vector.matrix.android.internal.crypto.algorithms.olm +import arrow.core.Try import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JsonDict -import im.vector.matrix.android.internal.crypto.MXDecryptionException import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting @@ -41,30 +41,29 @@ internal class MXOlmDecryption( private val credentials: Credentials) : IMXDecrypting { - @Throws(MXDecryptionException::class) - override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { + override suspend fun decryptEvent(event: Event, timeline: String): Try { val olmEventContent = event.content.toModel() ?: run { Timber.e("## decryptEvent() : bad event format") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_EVENT_FORMAT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, + MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)) } val cipherText = olmEventContent.ciphertext ?: run { Timber.e("## decryptEvent() : missing cipher text") - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT, + MXCryptoError.MISSING_CIPHER_TEXT_REASON)) } val senderKey = olmEventContent.senderKey ?: run { Timber.e("## decryptEvent() : missing sender key") - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, + MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) } val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run { Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients") - throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, + MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)) } // The message for myUser @@ -74,14 +73,14 @@ internal class MXOlmDecryption( if (decryptedPayload == null) { Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, + MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) } val payloadString = convertFromUTF8(decryptedPayload) if (payloadString == null) { Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, + MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) } val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE) @@ -89,73 +88,72 @@ internal class MXOlmDecryption( if (payload == null) { Timber.e("## decryptEvent failed : null payload") - throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, + MXCryptoError.MISSING_CIPHER_TEXT_REASON)) } val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { Timber.e("## decryptEvent() : bad olmPayloadContent format") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, + MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)) } if (olmPayloadContent.recipient.isNullOrBlank()) { val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") Timber.e("## decryptEvent() : $reason") - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, reason)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, + reason)) } if (olmPayloadContent.recipient != credentials.userId) { Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, + String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) } val recipientKeys = olmPayloadContent.recipient_keys ?: run { Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack") - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, + String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) } val ed25519 = recipientKeys["ed25519"] if (ed25519 != olmDevice.deviceEd25519Key) { Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY, + MXCryptoError.BAD_RECIPIENT_KEY_REASON)) } if (olmPayloadContent.sender.isNullOrBlank()) { Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") - throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, + String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) } if (olmPayloadContent.sender != event.senderId) { Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}") - throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE, + String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) } if (olmPayloadContent.room_id != event.roomId) { Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}") - throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ROOM_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM, + String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) } val keys = olmPayloadContent.keys ?: run { Timber.e("## decryptEvent failed : null keys") - throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)) + return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, + MXCryptoError.MISSING_CIPHER_TEXT_REASON)) } - val result = MXEventDecryptionResult() - result.clearEvent = payload - result.senderCurve25519Key = senderKey - result.claimedEd25519Key = keys["ed25519"] - - return result + return Try.just(MXEventDecryptionResult( + clearEvent = payload, + senderCurve25519Key = senderKey, + claimedEd25519Key = keys["ed25519"] + )) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/MXDecryptionResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt similarity index 79% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/MXDecryptionResult.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt index cb1e1f70..8a28e492 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/MXDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/OlmDecryptionResult.kt @@ -14,32 +14,32 @@ * limitations under the License. */ -package im.vector.matrix.android.internal.crypto.algorithms +package im.vector.matrix.android.internal.crypto.algorithms.olm import im.vector.matrix.android.api.util.JsonDict /** * This class represents the decryption result. */ -data class MXDecryptionResult( +data class OlmDecryptionResult( /** * The decrypted payload (with properties 'type', 'content') */ - var payload: JsonDict? = null, + val payload: JsonDict? = null, /** * keys that the sender of the event claims ownership of: * map from key type to base64-encoded key. */ - var keysClaimed: Map? = null, + val keysClaimed: Map? = null, /** * The curve25519 key that the sender of the event is known to have ownership of. */ - var senderKey: String? = null, + val senderKey: String? = null, /** * Devices which forwarded this session to us (normally empty). */ - var forwardingCurve25519KeyChain: List? = null -) \ No newline at end of file + val forwardingCurve25519KeyChain: List? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXEncryptEventContentResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXEncryptEventContentResult.kt index 159ae0d5..7eaeb5bb 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXEncryptEventContentResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXEncryptEventContentResult.kt @@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content data class MXEncryptEventContentResult( /** - * The event content + * The encrypted event content */ val eventContent: Content, /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 7c3bfabf..9865614c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate import im.vector.matrix.android.internal.session.SessionScope +import io.realm.Realm import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.where @@ -47,6 +48,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati * Memory cache, to correctly release JNI objects * ========================================================================================== */ + // A realm instance, for faster future getInstance. Do not use it + private var realmLocker: Realm? = null + // The olm account private var olmAccount: OlmAccount? = null @@ -86,6 +90,8 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati } override fun open() { + realmLocker = Realm.getInstance(realmConfiguration) + // Ensure CryptoMetadataEntity is inserted in DB doWithRealm(realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() @@ -131,6 +137,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati inboundGroupSessionToRelease.clear() olmAccount?.releaseAccount() + + realmLocker?.close() + realmLocker = null } override fun storeDeviceId(deviceId: String) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index fc4bf8c4..162b9d3a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.CancelableBag -import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.util.CancelableWork @@ -82,11 +81,9 @@ internal class DefaultSendService @Inject constructor(private val context: Conte } override fun sendMedias(attachments: List): Cancelable { - val cancelableBag = CancelableBag() - attachments.forEach { - sendMedia(it).addTo(cancelableBag) + return attachments.mapTo(CancelableBag()) { + sendMedia(it) } - return cancelableBag } override fun redactEvent(event: Event, reason: String?): Cancelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index a291dfdc..542270c3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.CancelableBag -import im.vector.matrix.android.api.util.addTo import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.database.mapper.asDomain @@ -393,7 +392,7 @@ internal class DefaultTimeline( limit = limit) Timber.v("Should fetch $limit items $direction") - paginationTask.configureWith(params) + cancelableBag += paginationTask.configureWith(params) .enableRetry() .dispatchTo(object : MatrixCallback { override fun onSuccess(data: TokenChunkEventPersistor.Result) { @@ -412,7 +411,6 @@ internal class DefaultTimeline( } }) .executeBy(taskExecutor) - .addTo(cancelableBag) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index 8992091b..5ee64b1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -17,15 +17,14 @@ package im.vector.matrix.android.internal.session.sync import android.text.TextUtils +import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.internal.crypto.CryptoManager -import im.vector.matrix.android.internal.crypto.MXDecryptionException import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse import timber.log.Timber @@ -40,7 +39,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: // Decrypt event if necessary decryptEvent(event, null) if (TextUtils.equals(event.getClearType(), EventType.MESSAGE) - && event.mClearEvent?.content?.toModel()?.type == "m.bad.encrypted") { + && event.mClearEvent?.content?.toModel()?.type == "m.bad.encrypted") { Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content) } else { sasVerificationService.onToDeviceEvent(event) @@ -66,8 +65,8 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: var result: MXEventDecryptionResult? = null try { result = cryptoManager.decryptEvent(event, timelineId ?: "") - } catch (exception: MXDecryptionException) { - event.setCryptoError(exception.cryptoError) + } catch (exception: MXCryptoError) { + event.setCryptoError(exception) } if (null != result) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt index 4e7f0c7a..a83ab013 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/StringUtils.kt @@ -25,19 +25,13 @@ import timber.log.Timber * @return the utf-8 string */ fun convertToUTF8(s: String): String? { - var out: String? = s - - if (null != out) { - try { - val bytes = out.toByteArray(charset("UTF-8")) - out = String(bytes) - } catch (e: Exception) { - Timber.e(e, "## convertToUTF8() failed") - } - + return try { + val bytes = s.toByteArray(Charsets.UTF_8) + String(bytes) + } catch (e: Exception) { + Timber.e(e, "## convertToUTF8() failed") + null } - - return out } /** @@ -47,17 +41,11 @@ fun convertToUTF8(s: String): String? { * @return the utf-16 string */ fun convertFromUTF8(s: String): String? { - var out: String? = s - - if (null != out) { - try { - val bytes = out.toByteArray() - out = String(bytes, charset("UTF-8")) - } catch (e: Exception) { - Timber.e(e, "## convertFromUTF8() failed") - } - + return try { + val bytes = s.toByteArray() + String(bytes, Charsets.UTF_8) + } catch (e: Exception) { + Timber.e(e, "## convertFromUTF8() failed") + null } - - return out } diff --git a/vector/src/main/java/im/vector/riotx/EmojiCompatFontProvider.kt b/vector/src/main/java/im/vector/riotx/EmojiCompatFontProvider.kt index 994cb136..24e7e6d2 100644 --- a/vector/src/main/java/im/vector/riotx/EmojiCompatFontProvider.kt +++ b/vector/src/main/java/im/vector/riotx/EmojiCompatFontProvider.kt @@ -50,7 +50,7 @@ class EmojiCompatFontProvider @Inject constructor(): FontsContractCompat.FontReq } fun addListener(listener: FontProviderListener) { - if (!listeners.contains(listener)) { + if (listener !in listeners) { listeners.add(listener) } } diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/DialogAdapter.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/DialogAdapter.kt index 0cc84799..49d865e8 100644 --- a/vector/src/main/java/im/vector/riotx/core/dialogs/DialogAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/core/dialogs/DialogAdapter.kt @@ -26,14 +26,16 @@ import im.vector.riotx.R internal abstract class DialogAdapter(context: Context) : ArrayAdapter(context, R.layout.item_dialog) { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { - var view = convertView - if (view == null) { + val view: View + if (convertView == null) { view = LayoutInflater.from(context).inflate(R.layout.item_dialog, parent, false) view.tag = DialogListItemHolder(view) + } else { + view = convertView } - (view!!.tag as DialogListItemHolder).let { - it.icon.setImageResource(getItem(position).iconRes) - it.text.setText(getItem(position).titleRes) + with(view.tag as DialogListItemHolder) { + icon.setImageResource(getItem(position).iconRes) + text.setText(getItem(position).titleRes) } return view } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 74bff9e5..92f72de6 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -22,12 +22,7 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View -import androidx.annotation.AttrRes -import androidx.annotation.LayoutRes -import androidx.annotation.MainThread -import androidx.annotation.MenuRes -import androidx.annotation.Nullable -import androidx.annotation.StringRes +import androidx.annotation.* import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.isVisible @@ -260,8 +255,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - setResult(RESULT_CANCELED) - finish() + onBackPressed() return true } diff --git a/vector/src/main/java/im/vector/riotx/features/home/UserColor.kt b/vector/src/main/java/im/vector/riotx/features/home/UserColor.kt index ff05d98b..b9605833 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/UserColor.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/UserColor.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home import androidx.annotation.ColorRes import im.vector.riotx.R +import kotlin.math.abs @ColorRes @@ -36,7 +37,7 @@ fun getColorFromUserId(userId: String?): Int { i++ } - return when (Math.abs(hash) % 8 + 1) { + return when (abs(hash) % 8 + 1) { 1 -> R.color.riotx_username_1 2 -> R.color.riotx_username_2 3 -> R.color.riotx_username_3 diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 54c15685..0fd60157 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -28,7 +28,6 @@ import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_ -import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory import me.gujun.android.span.span import javax.inject.Inject @@ -49,10 +48,16 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat EventType.ENCRYPTED == event.root.getClearType() -> { val cryptoError = event.root.mCryptoError val errorDescription = - if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) { - stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) + if (cryptoError is MXCryptoError.Base) { + if (cryptoError.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { + stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id) + } else { + // TODO i18n + cryptoError.technicalMessage + } } else { - cryptoError?.message + // Cannot happen (for now) + "Other error" } val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription) @@ -77,7 +82,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat })) .longClickListener { view -> return@longClickListener callback?.onEventLongClicked(informationData, null, view) - ?: false + ?: false } } else -> null diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index cb9d354b..4a927b19 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -83,7 +83,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me NoticeItem_() .avatarRenderer(avatarRenderer) .informationData(informationData) - .noticeText("{ \"type\": ${event.root.type} }") + .noticeText("{ \"type\": ${event.root.getClearType()} }") .highlighted(highlight) .baseCallback(callback) } diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index 0f92cbff..412043e8 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -28,6 +28,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.utils.DimensionUtils.dpToPx import kotlinx.android.parcel.Parcelize +import timber.log.Timber import java.io.File import javax.inject.Inject @@ -94,6 +95,11 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val fullSize = contentUrlResolver.resolveFullSize(data.url) val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) + if (fullSize.isNullOrBlank() || thumbnail.isNullOrBlank()) { + Timber.w("Invalid urls") + return + } + // TODO DECRYPT_FILE Decrypt file imageView.showImage( Uri.parse(thumbnail),