diff --git a/CHANGES.md b/CHANGES.md index 55e9161efb..4c9385e317 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,39 @@ +## Changes in 0.23.18 (2022-09-07) + +✨ Features + +- MXKeyBackup: Add support for symmetric key backups. ([#1542](https://github.com/matrix-org/matrix-ios-sdk/pull/1542)) +- CryptoSDK: Outgoing SAS User Verification Flow ([#6443](https://github.com/vector-im/element-ios/issues/6443)) +- CryptoV2: Self-verification flow ([#6589](https://github.com/vector-im/element-ios/issues/6589)) + +🙌 Improvements + +- Allow setting room alias regardless of join rule ([#1559](https://github.com/matrix-org/matrix-ios-sdk/pull/1559)) +- Crypto: Cache inbound group sessions when decrypting ([#1566](https://github.com/matrix-org/matrix-ios-sdk/pull/1566)) +- Crypto: Create lazy in-memory room encryptors ([#1570](https://github.com/matrix-org/matrix-ios-sdk/pull/1570)) +- App Layout: Increased store version to force clear cache ([#6616](https://github.com/vector-im/element-ios/issues/6616)) + +🐛 Bugfixes + +- Fix incoming calls sometimes ringing after being answered on another client ([#6614](https://github.com/vector-im/element-ios/issues/6614)) + +🧱 Build + +- Xcode project(s) updated via Xcode recommended setting ([#1543](https://github.com/matrix-org/matrix-ios-sdk/pull/1543)) +- MXLog: Ensure MXLogLevel.none works if it is set after another log level has already been configured. ([#1550](https://github.com/matrix-org/matrix-ios-sdk/issues/1550)) + +📄 Documentation + +- README: Update the badge header ([#1569](https://github.com/matrix-org/matrix-ios-sdk/pull/1569)) +- Update README for correct Swift usage. ([#1552](https://github.com/matrix-org/matrix-ios-sdk/issues/1552)) + +Others + +- Crypto: User and device identity objects ([#1531](https://github.com/matrix-org/matrix-ios-sdk/pull/1531)) +- Analytics: Log all errors to analytics ([#1558](https://github.com/matrix-org/matrix-ios-sdk/pull/1558)) +- Improve MXLog file formatting and fix log message format ([#1564](https://github.com/matrix-org/matrix-ios-sdk/pull/1564)) + + ## Changes in 0.23.17 (2022-08-31) 🙌 Improvements diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index bf32708555..cffb2f7207 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixSDK" - s.version = "0.23.17" + s.version = "0.23.18" s.summary = "The iOS SDK to build apps compatible with Matrix (https://www.matrix.org)" s.description = <<-DESC diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 40cdc589fa..188a5c8cdc 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1907,6 +1907,8 @@ EDD578EA2881C37C006739DD /* MXCryptoUserIdentityWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578E02881C37C006739DD /* MXCryptoUserIdentityWrapper.swift */; }; EDD578EC2881C38C006739DD /* MXCrossSigningV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */; }; EDD578ED2881C38C006739DD /* MXCrossSigningV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */; }; + EDE1B13B28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */; }; + EDE1B13C28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */; }; EDF1B6902876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6912876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6932876CD8600BBBCEE /* MXTaskQueueUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */; }; @@ -2963,6 +2965,7 @@ EDD578DF2881C37C006739DD /* MXCryptoDeviceWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoDeviceWrapper.swift; sourceTree = ""; }; EDD578E02881C37C006739DD /* MXCryptoUserIdentityWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoUserIdentityWrapper.swift; sourceTree = ""; }; EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2.swift; sourceTree = ""; }; + EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2UnitTests.swift; sourceTree = ""; }; EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueue.swift; sourceTree = ""; }; EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueueUnitTests.swift; sourceTree = ""; }; EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsEnumeratorDataSourceStub.swift; sourceTree = ""; }; @@ -5259,6 +5262,7 @@ children = ( ED8F1D1528857FDA00F897E7 /* Data */, ED8F1D242885A39800F897E7 /* MXCrossSigningInfoSourceUnitTests.swift */, + EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */, ); path = CrossSigning; sourceTree = ""; @@ -6945,6 +6949,7 @@ 32792BE12296C64200F4FC9D /* MXAggregatedEditsTests.m in Sources */, 329571931B0240CE00ABB3BA /* MXVoIPTests.m in Sources */, ED8F1D322885AC5700F897E7 /* Device+Stub.swift in Sources */, + EDE1B13B28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */, EC746C56274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */, ED8F1D252885A39800F897E7 /* MXCrossSigningInfoSourceUnitTests.swift in Sources */, 32A27D1F19EC335300BAFADE /* MXRoomTests.m in Sources */, @@ -7543,6 +7548,7 @@ B1E09A332397FD750057C069 /* MXRoomStateTests.m in Sources */, 18937E7D273A5AE500902626 /* MXPollRelationTests.m in Sources */, B1E09A352397FD7D0057C069 /* MXEventTests.m in Sources */, + EDE1B13C28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */, A816248525F60D0300A46F05 /* MXDeviceListOperationsPoolUnitTests.swift in Sources */, EC746C57274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */, B1E09A312397FD750057C069 /* MXSessionTests.m in Sources */, diff --git a/MatrixSDK/Contrib/Swift/MXSession.swift b/MatrixSDK/Contrib/Swift/MXSession.swift index 0116312e6f..11d7dd578d 100644 --- a/MatrixSDK/Contrib/Swift/MXSession.swift +++ b/MatrixSDK/Contrib/Swift/MXSession.swift @@ -163,6 +163,7 @@ public extension MXSession { let parameters = MXRoomCreationParameters() parameters.name = name parameters.topic = topic + parameters.roomAlias = aliasLocalPart let stateEventBuilder = MXRoomInitialStateEventBuilder() @@ -173,7 +174,6 @@ public extension MXSession { } parameters.preset = kMXRoomPresetPublicChat parameters.visibility = kMXRoomDirectoryVisibilityPublic - parameters.roomAlias = aliasLocalPart let guestAccessStateEvent = stateEventBuilder.buildGuestAccessEvent(withAccess: .canJoin) parameters.addOrUpdateInitialStateEvent(guestAccessStateEvent) let historyVisibilityStateEvent = stateEventBuilder.buildHistoryVisibilityEvent(withVisibility: .worldReadable) diff --git a/MatrixSDK/Crypto/CrossSigning/JSONModels/MXCrossSigningKey.m b/MatrixSDK/Crypto/CrossSigning/JSONModels/MXCrossSigningKey.m index ac2ea52693..c648633deb 100644 --- a/MatrixSDK/Crypto/CrossSigning/JSONModels/MXCrossSigningKey.m +++ b/MatrixSDK/Crypto/CrossSigning/JSONModels/MXCrossSigningKey.m @@ -17,6 +17,7 @@ #import "MXCrossSigningKey.h" #import "MXKey.h" +#import "MXCryptoConstants.h" #pragma mark - Constants diff --git a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningTools.m b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningTools.m index 21003ba876..a80479b169 100644 --- a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningTools.m +++ b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningTools.m @@ -18,6 +18,7 @@ #import "MXCryptoTools.h" #import "MXKey.h" +#import "MXCryptoConstants.h" #pragma mark - Constants diff --git a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift index 9fafbe4983..d4ab28c349 100644 --- a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift +++ b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift @@ -33,8 +33,17 @@ class MXCrossSigningV2: MXCrossSigning { } override var state: MXCrossSigningState { - log.debug("Only partial implementation") - return hasAllPrivateKeys ? .canCrossSign : .notBootstrapped + if hasAllPrivateKeys { + return .canCrossSign + } else if let info = info { + if info.trustLevel.isVerified { + return .trustCrossSigning + } else { + return .crossSigningExists + } + } else { + return .notBootstrapped + } } override var canTrustCrossSigning: Bool { @@ -46,16 +55,20 @@ class MXCrossSigningV2: MXCrossSigning { } override var hasAllPrivateKeys: Bool { - let status = machine.crossSigningStatus() + let status = crossSigning.crossSigningStatus() return status.hasMaster && status.hasSelfSigning && status.hasUserSigning } - private let machine: MXCryptoMachine + private let crossSigning: MXCryptoCrossSigning + private let infoSource: MXCrossSigningInfoSource + private var info: MXCrossSigningInfo? private let restClient: MXRestClient + private let log = MXNamedLog(name: "MXCrossSigningV2") - init(machine: MXCryptoMachine, restClient: MXRestClient) { - self.machine = machine + init(crossSigning: MXCryptoCrossSigning, restClient: MXRestClient) { + self.crossSigning = crossSigning + self.infoSource = MXCrossSigningInfoSource(source: crossSigning) self.restClient = restClient } @@ -67,7 +80,7 @@ class MXCrossSigningV2: MXCrossSigning { Task { do { let authParams = try await authParameters(password: password) - try await machine.bootstrapCrossSigning(authParams: authParams) + try await crossSigning.bootstrapCrossSigning(authParams: authParams) await MainActor.run { success() } @@ -87,7 +100,7 @@ class MXCrossSigningV2: MXCrossSigning { ) { Task { do { - try await machine.bootstrapCrossSigning(authParams: authParams) + try await crossSigning.bootstrapCrossSigning(authParams: authParams) await MainActor.run { success() } @@ -104,8 +117,21 @@ class MXCrossSigningV2: MXCrossSigning { success: ((Bool) -> Void)?, failure: ((Swift.Error) -> Void)? = nil ) { - log.debug("Not implemented") - success?(true) + Task { + do { + try await crossSigning.downloadKeys(users: [crossSigning.userId]) + info = infoSource.crossSigningInfo(userId: crossSigning.userId) + + await MainActor.run { + success?(true) + } + } catch { + log.error("Cannot refresh cross signing state", context: error) + await MainActor.run { + failure?(error) + } + } + } } override func crossSignDevice( diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index daef68ccd8..1f1d73021a 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -98,6 +98,33 @@ class MXCryptoMachine { } } +@available(iOS 13.0.0, *) +extension MXCryptoMachine: MXCryptoIdentity { + var userId: String { + return machine.userId() + } + + var deviceId: String { + return machine.deviceId() + } + + var deviceCurve25519Key: String? { + guard let key = machine.identityKeys()[kMXKeyCurve25519Type] else { + log.error("Cannot get device curve25519 key") + return nil + } + return key + } + + var deviceEd25519Key: String? { + guard let key = machine.identityKeys()[kMXKeyEd25519Type] else { + log.error("Cannot get device ed25519 key") + return nil + } + return key + } +} + @available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoSyncing { func handleSyncResponse( @@ -105,7 +132,7 @@ extension MXCryptoMachine: MXCryptoSyncing { deviceLists: MXDeviceListResponse?, deviceOneTimeKeysCounts: [String: NSNumber], unusedFallbackKeys: [String]? - ) throws { + ) throws -> MXToDeviceSyncResponse { let events = toDevice?.jsonString() ?? "[]" let deviceChanges = DeviceLists( changed: deviceLists?.changed ?? [], @@ -120,9 +147,13 @@ extension MXCryptoMachine: MXCryptoSyncing { unusedFallbackKeys: unusedFallbackKeys ) - if let result = MXTools.deserialiseJSONString(result) as? [String: Any], !result.isEmpty { - log(error: "Result processing not implemented \(result)") + guard let json = MXTools.deserialiseJSONString(result) as? [AnyHashable: Any] else { + log.error("Result cannot be serialized", context: [ + "result": result + ]) + return MXToDeviceSyncResponse() } + return MXToDeviceSyncResponse(fromJSON: json) } func completeSync() async throws { @@ -215,22 +246,6 @@ extension MXCryptoMachine: MXCryptoSyncing { @available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoDevicesSource { - var deviceCurve25519Key: String? { - guard let key = machine.identityKeys()["curve25519"] else { - log.error("Cannot get device curve25519 key") - return nil - } - return key - } - - var deviceEd25519Key: String? { - guard let key = machine.identityKeys()["ed25519"] else { - log.error("Cannot get device ed25519 key") - return nil - } - return key - } - func devices(userId: String) -> [Device] { do { return try machine.getUserDevices(userId: userId, timeout: 0) @@ -269,6 +284,12 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource { return nil } } + + func downloadKeys(users: [String]) async throws { + try await handleRequest( + .keysQuery(requestId: UUID().uuidString, users: users) + ) + } } @available(iOS 13.0.0, *) @@ -371,7 +392,15 @@ extension MXCryptoMachine: MXCryptoCrossSigning { } @available(iOS 13.0.0, *) -extension MXCryptoMachine: MXCryptoVerification { +extension MXCryptoMachine: MXCryptoVerificationRequesting { + func requestSelfVerification(methods: [String]) async throws -> VerificationRequest { + guard let result = try machine.requestSelfVerification(methods: methods) else { + throw Error.missingVerification + } + try await handleOutgoingVerificationRequest(result.request) + return result.verification + } + func requestVerification(userId: String, roomId: String, methods: [String]) async throws -> VerificationRequest { guard let content = try machine.verificationRequestContent(userId: userId, methods: methods) else { throw Error.missingVerificationContent @@ -403,18 +432,47 @@ extension MXCryptoMachine: MXCryptoVerification { return machine.getVerificationRequest(userId: userId, flowId: flowId) } - func verification(userId: String, flowId: String) -> Verification? { - return machine.getVerification(userId: userId, flowId: flowId) + func acceptVerificationRequest(userId: String, flowId: String, methods: [String]) async throws { + guard let request = machine.acceptVerificationRequest(userId: userId, flowId: flowId, methods: methods) else { + throw Error.missingVerificationRequest + } + try await handleOutgoingVerificationRequest(request) } - - func beginSasVerification(userId: String, flowId: String) async throws -> Sas { - guard let result = try machine.startSasVerification(userId: userId, flowId: flowId) else { - throw Error.missingVerification + + func cancelVerification(userId: String, flowId: String, cancelCode: String) async throws { + guard let request = machine.cancelVerification(userId: userId, flowId: flowId, cancelCode: cancelCode) else { + throw Error.cannotCancelVerification } - try await handleOutgoingVerificationRequest(result.request) - return result.sas + try await handleOutgoingVerificationRequest(request) } + + // MARK: - Private + + private func handleOutgoingVerificationRequest(_ request: OutgoingVerificationRequest) async throws { + switch request { + case .toDevice(_, let eventType, let body): + try await requests.sendToDevice( + request: .init( + eventType: eventType, + body: body + ) + ) + case .inRoom(_, let roomId, let eventType, let content): + let _ = try await sendRoomMessage( + roomId: roomId, + eventType: eventType, + content: content + ) + } + } +} +@available(iOS 13.0.0, *) +extension MXCryptoMachine: MXCryptoVerifying { + func verification(userId: String, flowId: String) -> Verification? { + return machine.getVerification(userId: userId, flowId: flowId) + } + func confirmVerification(userId: String, flowId: String) async throws { let result = try machine.confirmVerification(userId: userId, flowId: flowId) guard let result = result else { @@ -435,10 +493,21 @@ extension MXCryptoMachine: MXCryptoVerification { try await group.waitForAll() } } +} + +@available(iOS 13.0.0, *) +extension MXCryptoMachine: MXCryptoSASVerifying { + func startSasVerification(userId: String, flowId: String) async throws -> Sas { + guard let result = try machine.startSasVerification(userId: userId, flowId: flowId) else { + throw Error.missingVerification + } + try await handleOutgoingVerificationRequest(result.request) + return result.sas + } - func cancelVerification(userId: String, flowId: String, cancelCode: String) async throws { - guard let request = machine.cancelVerification(userId: userId, flowId: flowId, cancelCode: cancelCode) else { - throw Error.cannotCancelVerification + func acceptSasVerification(userId: String, flowId: String) async throws { + guard let request = machine.acceptSasVerification(userId: userId, flowId: flowId) else { + throw Error.missingVerification } try await handleOutgoingVerificationRequest(request) } @@ -449,21 +518,6 @@ extension MXCryptoMachine: MXCryptoVerification { } return indexes.map(Int.init) } - - // MARK: - Private - - private func handleOutgoingVerificationRequest(_ request: OutgoingVerificationRequest) async throws { - guard case .inRoom(let requestId, let roomId, let eventType, let content) = request else { - assertionFailure("Not yet implemented") - return - } - - let _ = try await sendRoomMessage( - roomId: roomId, - eventType: eventType, - content: content - ) - } } @available(iOS 13.0.0, *) diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index be6a14d7c2..b9e92c1c40 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -22,53 +22,81 @@ import MatrixSDKCrypto /// A set of protocols defining the functionality in `MatrixSDKCrypto` and separating them into logical units +/// Cryptographic identity of the currently signed-in user @available(iOS 13.0.0, *) -protocol MXCryptoSyncing { +protocol MXCryptoIdentity { + var userId: String { get } + var deviceId: String { get } + var deviceCurve25519Key: String? { get } + var deviceEd25519Key: String? { get } +} + +/// Handler for cryptographic events in the sync loop +@available(iOS 13.0.0, *) +protocol MXCryptoSyncing: MXCryptoIdentity { func handleSyncResponse( toDevice: MXToDeviceSyncResponse?, deviceLists: MXDeviceListResponse?, deviceOneTimeKeysCounts: [String: NSNumber], unusedFallbackKeys: [String]? - ) throws + ) throws -> MXToDeviceSyncResponse func completeSync() async throws } -protocol MXCryptoDevicesSource { - var deviceCurve25519Key: String? { get } - var deviceEd25519Key: String? { get } +/// Source of user devices and their cryptographic trust status +@available(iOS 13.0.0, *) +protocol MXCryptoDevicesSource: MXCryptoIdentity { func device(userId: String, deviceId: String) -> Device? func devices(userId: String) -> [Device] } -protocol MXCryptoUserIdentitySource { +/// Source of user identities and their cryptographic trust status +@available(iOS 13.0.0, *) +protocol MXCryptoUserIdentitySource: MXCryptoIdentity { func userIdentity(userId: String) -> UserIdentity? func isUserVerified(userId: String) -> Bool + func downloadKeys(users: [String]) async throws } +/// Event encryption and decryption @available(iOS 13.0.0, *) -protocol MXCryptoEventEncrypting { +protocol MXCryptoEventEncrypting: MXCryptoIdentity { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] func decryptEvent(_ event: MXEvent) throws -> MXEventDecryptionResult } +/// Cross-signing functionality @available(iOS 13.0.0, *) -protocol MXCryptoCrossSigning { +protocol MXCryptoCrossSigning: MXCryptoUserIdentitySource { func crossSigningStatus() -> CrossSigningStatus func bootstrapCrossSigning(authParams: [AnyHashable: Any]) async throws } +/// Lifecycle of verification request @available(iOS 13.0.0, *) -protocol MXCryptoVerification { +protocol MXCryptoVerificationRequesting: MXCryptoIdentity { + func requestSelfVerification(methods: [String]) async throws -> VerificationRequest func requestVerification(userId: String, roomId: String, methods: [String]) async throws -> VerificationRequest func verificationRequest(userId: String, flowId: String) -> VerificationRequest? - + func acceptVerificationRequest(userId: String, flowId: String, methods: [String]) async throws + func cancelVerification(userId: String, flowId: String, cancelCode: String) async throws +} + +/// Lifecycle of verification transaction +@available(iOS 13.0.0, *) +protocol MXCryptoVerifying: MXCryptoIdentity { func verification(userId: String, flowId: String) -> Verification? - func beginSasVerification(userId: String, flowId: String) async throws -> Sas func confirmVerification(userId: String, flowId: String) async throws func cancelVerification(userId: String, flowId: String, cancelCode: String) async throws - +} + +/// Lifecycle of SAS-specific verification transaction +@available(iOS 13.0.0, *) +protocol MXCryptoSASVerifying: MXCryptoVerifying { + func startSasVerification(userId: String, flowId: String) async throws -> Sas + func acceptSasVerification(userId: String, flowId: String) async throws func emojiIndexes(sas: Sas) throws -> [Int] } diff --git a/MatrixSDK/Crypto/Data/MXCryptoConstants.h b/MatrixSDK/Crypto/Data/MXCryptoConstants.h index ef9b004674..5d16b8f26d 100644 --- a/MatrixSDK/Crypto/Data/MXCryptoConstants.h +++ b/MatrixSDK/Crypto/Data/MXCryptoConstants.h @@ -18,6 +18,13 @@ #import +/** + Key types + */ +FOUNDATION_EXPORT NSString *const kMXKeyCurve25519Type; +FOUNDATION_EXPORT NSString *const kMXKeySignedCurve25519Type; +FOUNDATION_EXPORT NSString *const kMXKeyEd25519Type; + /** Matrix algorithm tag for olm. */ diff --git a/MatrixSDK/Crypto/Data/MXCryptoConstants.m b/MatrixSDK/Crypto/Data/MXCryptoConstants.m index a804a41479..ed0a9d618f 100644 --- a/MatrixSDK/Crypto/Data/MXCryptoConstants.m +++ b/MatrixSDK/Crypto/Data/MXCryptoConstants.m @@ -17,6 +17,10 @@ #import "MXCryptoConstants.h" +NSString *const kMXKeyCurve25519Type = @"curve25519"; +NSString *const kMXKeySignedCurve25519Type = @"signed_curve25519"; +NSString *const kMXKeyEd25519Type = @"ed25519"; + NSString *const kMXCryptoOlmAlgorithm = @"m.olm.v1.curve25519-aes-sha2"; NSString *const kMXCryptoMegolmAlgorithm = @"m.megolm.v1.aes-sha2"; NSString *const kMXCryptoCurve25519KeyBackupAlgorithm = @"m.megolm_backup.v1.curve25519-aes-sha2"; diff --git a/MatrixSDK/Crypto/Data/MXKey.h b/MatrixSDK/Crypto/Data/MXKey.h index ed10952233..4ae65cb719 100644 --- a/MatrixSDK/Crypto/Data/MXKey.h +++ b/MatrixSDK/Crypto/Data/MXKey.h @@ -18,13 +18,6 @@ #import "MXUsersDevicesMap.h" -/** - Key types. - */ -FOUNDATION_EXPORT NSString *const kMXKeyCurve25519Type; -FOUNDATION_EXPORT NSString *const kMXKeySignedCurve25519Type; -FOUNDATION_EXPORT NSString *const kMXKeyEd25519Type; - /** A `MXKey` instance stores a key data shared for Matrix cryptography. */ diff --git a/MatrixSDK/Crypto/Data/MXKey.m b/MatrixSDK/Crypto/Data/MXKey.m index 703f64c482..3c41e8dda8 100644 --- a/MatrixSDK/Crypto/Data/MXKey.m +++ b/MatrixSDK/Crypto/Data/MXKey.m @@ -16,10 +16,6 @@ #import "MXKey.h" -NSString *const kMXKeyCurve25519Type = @"curve25519"; -NSString *const kMXKeySignedCurve25519Type = @"signed_curve25519"; -NSString *const kMXKeyEd25519Type = @"ed25519"; - @interface MXKey() /** diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index a302c2ea77..b7d75ce1c4 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -889,7 +889,7 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS else { MXLogErrorDetails(@"[MXRealmCryptoStore] performSessionOperationWithDevice. Error: olm session not found", @{ - @"sessionId": sessionId + @"sessionId": sessionId ?: @"unknown" }); block(nil); } @@ -1008,7 +1008,7 @@ - (void)performSessionOperationWithGroupSessionWithId:(NSString*)sessionId sende else { MXLogErrorDetails(@"[MXRealmCryptoStore] performSessionOperationWithGroupSessionWithId. Error: Cannot build MXOlmInboundGroupSession for megolm session", @{ - @"sessionId": sessionId + @"sessionId": sessionId ?: @"unknown" }); block(nil); } @@ -1016,7 +1016,7 @@ - (void)performSessionOperationWithGroupSessionWithId:(NSString*)sessionId sende else { MXLogErrorDetails(@"[MXRealmCryptoStore] performSessionOperationWithGroupSessionWithId. Error: megolm session not found", @{ - @"sessionId": sessionId + @"sessionId": sessionId ?: @"unknown" }); block(nil); } diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index ba89f07da3..14fbb4eb91 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -663,7 +663,7 @@ - (MXEventDecryptionResult *)decryptEvent2:(MXEvent *)event inTimeline:(NSString { NSDictionary *details = @{ @"event_id": event.eventId ?: @"unknown", - @"error": result.error ?: @"unknown", + @"error": result.error ?: @"unknown" }; MXLogErrorDetails(@"[MXCrypto] decryptEvent", details); MXLogDebug(@"[MXCrypto] decryptEvent: Unable to decrypt event %@", event.JSONDictionary); @@ -2527,9 +2527,39 @@ - (NSDictionary*)encryptMessage:(NSDictionary*)payloadFields forDevices:(NSArray { if (![algorithm isEqualToString:kMXCryptoMegolmAlgorithm]) { + MXLogErrorDetails(@"[MXCrypto] getRoomEncryptor: algorithm is not supported", @{ + @"algorithm": algorithm ?: @"unknown" + }); + return nil; + } + + id alg = roomEncryptors[roomId]; + if (alg) + { + return alg; + } + + NSString *existingAlgorithm = [self.store algorithmForRoom:roomId]; + if ([algorithm isEqualToString:existingAlgorithm]) + { + MXLogErrorDetails(@"[MXCrypto] getRoomEncryptor: algorithm does not match the room", @{ + @"algorithm": algorithm ?: @"unknown" + }); return nil; } - return roomEncryptors[roomId]; + + Class algClass = [[MXCryptoAlgorithms sharedAlgorithms] encryptorClassForAlgorithm:algorithm]; + if (!algClass) + { + MXLogErrorDetails(@"[MXCrypto] getRoomEncryptor: cannot get encryptor for algorithm", @{ + @"algorithm": algorithm ?: @"unknown" + }); + return nil; + } + + alg = [[algClass alloc] initWithCrypto:self andRoom:roomId]; + roomEncryptors[roomId] = alg; + return alg; } - (NSDictionary*)signObject:(NSDictionary*)object diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index e9f02f3ace..4ffe745136 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -92,7 +92,7 @@ private class MXCryptoV2: MXCrypto { public override var backup: MXKeyBackup! { log.debug("Not implemented") - return nil + return MXKeyBackup() } public override var keyVerificationManager: MXKeyVerificationManager! { @@ -151,7 +151,7 @@ private class MXCryptoV2: MXCrypto { ) crossSign = MXCrossSigningV2( - machine: machine, + crossSigning: machine, restClient: restClient ) @@ -317,12 +317,13 @@ private class MXCryptoV2: MXCrypto { } do { - try machine.handleSyncResponse( + let toDevice = try machine.handleSyncResponse( toDevice: syncResponse.toDevice, deviceLists: syncResponse.deviceLists, deviceOneTimeKeysCounts: syncResponse.deviceOneTimeKeysCount ?? [:], unusedFallbackKeys: syncResponse.unusedFallbackKeys ) + keyVerification.handleDeviceEvents(toDevice.events) } catch { log.error("Cannot handle sync", context: error) } @@ -352,7 +353,6 @@ private class MXCryptoV2: MXCrypto { log.failure("Error processing outgoing requests", context: error) } } - keyVerification.updatePendingRequests() } // MARK: - Trust level @@ -436,11 +436,35 @@ private class MXCryptoV2: MXCrypto { success: ((MXUsersDevicesMap?, [String: MXCrossSigningInfo]?) -> Void)!, failure: ((Swift.Error?) -> Void)! ) -> MXHTTPOperation! { - // Note: Download keys currently ignores the `forceDownload` flag and returns local data only - success?( - deviceInfoSource.devicesMap(userIds: userIds), - crossSigningInfoSource.crossSigningInfo(userIds: userIds) - ) + guard let userIds = userIds else { + log.failure("Missing user ids") + return nil + } + + guard forceDownload else { + success?( + deviceInfoSource.devicesMap(userIds: userIds), + crossSigningInfoSource.crossSigningInfo(userIds: userIds) + ) + return MXHTTPOperation() + } + + Task { + do { + try await machine.downloadKeys(users: userIds) + await MainActor.run { + success?( + deviceInfoSource.devicesMap(userIds: userIds), + crossSigningInfoSource.crossSigningInfo(userIds: userIds) + ) + } + } catch { + await MainActor.run { + failure?(error) + } + } + } + return MXHTTPOperation() } diff --git a/MatrixSDK/Crypto/MXOlmDevice.m b/MatrixSDK/Crypto/MXOlmDevice.m index a3b9aa0383..e52a6f0749 100644 --- a/MatrixSDK/Crypto/MXOlmDevice.m +++ b/MatrixSDK/Crypto/MXOlmDevice.m @@ -28,6 +28,9 @@ #import "MXKeyProvider.h" #import "MXRawDataKey.h" +#import "MXCryptoConstants.h" + +NSInteger const kMXInboundGroupSessionCacheSize = 100; @interface MXOlmDevice () { @@ -53,8 +56,10 @@ @interface MXOlmDevice () // The store where crypto data is saved. @property (nonatomic, readonly) id store; -@end +// Cache to avoid refetching unchanged sessions from the crypto store +@property (nonatomic, strong) MXLRUCache *inboundGroupSessionCache; +@end @implementation MXOlmDevice @synthesize store; @@ -96,8 +101,10 @@ - (instancetype)initWithStore:(id)theStore inboundGroupSessionMessageIndexes = [NSMutableDictionary dictionary]; - _deviceCurve25519Key = olmAccount.identityKeys[@"curve25519"]; - _deviceEd25519Key = olmAccount.identityKeys[@"ed25519"]; + _deviceCurve25519Key = olmAccount.identityKeys[kMXKeyCurve25519Type]; + _deviceEd25519Key = olmAccount.identityKeys[kMXKeyEd25519Type]; + + _inboundGroupSessionCache = [[MXLRUCache alloc] initWithCapacity:kMXInboundGroupSessionCacheSize]; } return self; } @@ -394,7 +401,7 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId session.sharedHistory = sharedHistory; } - [store storeInboundGroupSessions:@[session]]; + [self storeInboundGroupSessions:@[session]]; return YES; } @@ -441,7 +448,7 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId [sessions addObject:session]; } - [store storeInboundGroupSessions:sessions]; + [self storeInboundGroupSessions:sessions]; return sessions; } @@ -462,7 +469,7 @@ - (MXDecryptionResult *)decryptGroupMessage:(NSString *)body MXDecryptionResult *result; - [store performSessionOperationWithGroupSessionWithId:sessionId senderKey:senderKey block:^(MXOlmInboundGroupSession *session) { + [self performGroupSessionOperationWithSessionId:sessionId senderKey:senderKey block:^(MXOlmInboundGroupSession *session) { *error = [self checkInboundGroupSession:session roomId:roomId]; if (*error) @@ -521,6 +528,43 @@ - (MXDecryptionResult *)decryptGroupMessage:(NSString *)body return result; } +- (void)performGroupSessionOperationWithSessionId:(NSString*)sessionId senderKey:(NSString*)senderKey block:(void (^)(MXOlmInboundGroupSession *inboundGroupSession))block +{ + // Based on a feature flag megolm decryption will either fetch a group session from the store on every decryption, + // or (if the flag is enabled) it will use LRU cache to avoid refetching unchanged sessions. + // + // Additionally the duration of each variant is tracked in analytics (if configured and enabled by the user) + // to allow performance comparison + // + // LRU cache variant will eventually become the default implementation if proved stable. + + BOOL enableCache = MXSDKOptions.sharedInstance.enableGroupSessionCache; + NSString *operation = enableCache ? @"megolm.decrypt.cache" : @"megolm.decrypt.store"; + StopDurationTracking stopTracking = [MXSDKOptions.sharedInstance.analyticsDelegate startDurationTrackingForName:@"MXOlmDevice" operation:operation]; + + if (enableCache) + { + @synchronized (self.inboundGroupSessionCache) + { + MXOlmInboundGroupSession *session = (MXOlmInboundGroupSession *)[self.inboundGroupSessionCache get:sessionId]; + if (!session) + { + session = [store inboundGroupSessionWithId:sessionId andSenderKey:senderKey]; + [self.inboundGroupSessionCache put:sessionId object:session]; + } + block(session); + } + } + else + { + [store performSessionOperationWithGroupSessionWithId:sessionId senderKey:senderKey block:block]; + } + if (stopTracking) + { + stopTracking(); + } +} + - (void)resetReplayAttackCheckInTimeline:(NSString*)timeline { [inboundGroupSessionMessageIndexes removeObjectForKey:timeline]; @@ -622,6 +666,21 @@ - (NSDictionary*)getInboundGroupSessionKey:(NSString*)roomId senderKey:(NSString return inboundGroupSessionKey; } +- (void)storeInboundGroupSessions:(NSArray *)sessions +{ + [store storeInboundGroupSessions:sessions]; + if (MXSDKOptions.sharedInstance.enableGroupSessionCache) + { + @synchronized (self.inboundGroupSessionCache) + { + for (MXOlmInboundGroupSession *session in sessions) + { + [self.inboundGroupSessionCache put:session.session.sessionIdentifier object:session]; + } + } + } +} + #pragma mark - OLMKitPickleKeyDelegate diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryService.h b/MatrixSDK/Crypto/Recovery/MXRecoveryService.h index 1e93f21f72..b0a8d7d9c3 100644 --- a/MatrixSDK/Crypto/Recovery/MXRecoveryService.h +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryService.h @@ -37,7 +37,7 @@ typedef NS_ENUM(NSInteger, MXRecoveryServiceErrorCode) /** - `MXRecoveryService` manages the backup of secrets/keys used by `MXCrypto``. + `MXRecoveryService` manages the backup of secrets/keys used by `MXCrypto`. It stores secrets stored locally (`MXCryptoStore`) on the homeserver SSSS (`MXSecretStorage`) and vice versa. diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.h b/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.h index 01fbd4107b..6f27cfa0ac 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.h +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.h @@ -85,12 +85,6 @@ FOUNDATION_EXPORT NSString *const MXKeyVerificationManagerNotificationTransactio #pragma mark - Requests -/** - The timeout for requests. - Default is 5 min. - */ -@property (nonatomic) NSTimeInterval requestTimeout; - /** Make a key verification request by to_device events. @@ -180,14 +174,6 @@ FOUNDATION_EXPORT NSString *const MXKeyVerificationManagerNotificationTransactio success:(void(^)(MXKeyVerification *keyVerification))success failure:(void(^)(NSError *error))failure; -/** - Extract the verification identifier from an event. - - @param event an event in the verification process. - @return the key verification id. Nil if the event is not a verification event. - */ -- (nullable NSString *)keyVerificationIdFromDMEvent:(MXEvent*)event; - /** Retrieve pending QR code transaction diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.m b/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.m index f4b0bcd98f..6034c6a93d 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.m +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManager.m @@ -79,6 +79,12 @@ @interface MXKeyVerificationManager () @property (nonatomic, strong) MXQRCodeDataBuilder *qrCodeDataBuilder; +/** + The timeout for requests. + Default is 5 min. + */ +@property (nonatomic) NSTimeInterval requestTimeout; + @end @implementation MXKeyVerificationManager diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift index 263b69ee1b..d1afdd7873 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift @@ -11,25 +11,36 @@ import Foundation import MatrixSDKCrypto +/// Result of processing updates on verification object (request or transaction) +/// after each sync loop +enum MXKeyVerificationUpdateResult { + // The object has not changed since last sync + case noUpdates + // The object's state has changed + case updated + // The object is no longer available (e.g. it was cancelled) + case removed +} + +@available(iOS 13.0.0, *) +typealias MXCryptoVerification = MXCryptoVerificationRequesting & MXCryptoSASVerifying + @available(iOS 13.0.0, *) class MXKeyVerificationManagerV2: MXKeyVerificationManager { - typealias GetOrCreateDMRoomId = (_ userId: String) async throws -> String - - override var requestTimeout: TimeInterval { - set { - log.debug("Not implemented") - } - get { - log.debug("Not implemented") - return 1000 - } + enum Error: Swift.Error { + case notSupported } + typealias GetOrCreateDMRoomId = (_ userId: String) async throws -> String + private let verification: MXCryptoVerification private let getOrCreateDMRoomId: GetOrCreateDMRoomId - private var requests: [MXKeyVerificationRequestV2] - private var transactions: [MXSASTransactionV2] + // We need to keep track of request / transaction objects by reference + // because various flows / screens subscribe to updates via global notifications + // posted through them + private var activeRequests: [String: MXKeyVerificationRequestV2] + private var activeTransactions: [String: MXSASTransactionV2] private let log = MXNamedLog(name: "MXKeyVerificationManagerV2") @@ -37,32 +48,46 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { self.verification = verification self.getOrCreateDMRoomId = getOrCreateDMRoomId - self.requests = [] - self.transactions = [] + self.activeRequests = [:] + self.activeTransactions = [:] super.init() } - func updatePendingRequests() { - for request in requests { - guard let req = verification.verificationRequest(userId: request.otherUser, flowId: request.requestId) else { - log.debug("No request found for id \(request.requestId)") - continue - } - request.update(request: req) - } + func handleDeviceEvents(_ events: [MXEvent]) { + // We only have to manually handle request and start events, + // because they require creation of new objects observed by the UI. + // The other events (e.g. cancellation) are handled automatically + // by `MXCryptoVerification` + let eventTypes: Set = [ + kMXMessageTypeKeyVerificationRequest, + kMXEventTypeStringKeyVerificationStart + ] - for transaction in transactions { - guard let verification = verification.verification(userId: transaction.otherUserId, flowId: transaction.transactionId) else { - log.debug("No transaction found for id \(transaction.transactionId)") + for event in events { + guard eventTypes.contains(event.type) else { continue } - guard case .sasV1(let sas) = verification else { - assertionFailure("Not implemented") + + guard + let userId = event.sender, + let flowId = event.content["transaction_id"] as? String + else { + log.error("Missing userId or flowId in event") continue } - transaction.update(sas: sas) + + switch event.type { + case kMXMessageTypeKeyVerificationRequest: + incomingVerificationRequest(userId: userId, flowId: flowId) + case kMXEventTypeStringKeyVerificationStart: + incomingVerificationStart(userId: userId, flowId: flowId) + default: + log.failure("Event type should not be handled by key verification", context: event.type) + } } + + updatePendingVerification() } override func requestVerificationByToDevice( @@ -70,10 +95,29 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { deviceIds: [String]?, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, - failure: @escaping (Error) -> Void + failure: @escaping (Swift.Error) -> Void ) { - log.debug("Not implemented") - success(MXDefaultKeyVerificationRequest()) + guard userId == verification.userId else { + log.failure("To-device verification with other users is not supported") + failure(Error.notSupported) + return + } + + Task { + do { + let req = try await verification.requestSelfVerification(methods: methods) + + let request = addRequest(for: req, transport: .toDevice) + await MainActor.run { + success(request) + } + } catch { + log.error("Cannot request verification", context: error) + await MainActor.run { + failure(error) + } + } + } } override func requestVerificationByDM( @@ -82,7 +126,7 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { fallbackText: String, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, - failure: @escaping (Error) -> Void + failure: @escaping (Swift.Error) -> Void ) { Task { do { @@ -92,10 +136,8 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { roomId: roomId, methods: methods ) - let request = MXKeyVerificationRequestV2(request: req) { [weak self] request, code in - self?.cancel(request: request, code: code) - } - requests.append(request) + + let request = addRequest(for: req, transport: .directMessage) await MainActor.run { success(request) } @@ -108,16 +150,12 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { } } - override var pendingRequests: [MXKeyVerificationRequest] { - return requests - } - override func beginKeyVerification( withUserId userId: String, andDeviceId deviceId: String, method: String, success: @escaping (MXKeyVerificationTransaction) -> Void, - failure: @escaping (Error) -> Void + failure: @escaping (Swift.Error) -> Void ) { log.debug("Not implemented") success(MXDefaultKeyVerificationTransaction()) @@ -127,24 +165,12 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { from request: MXKeyVerificationRequest, method: String, success: @escaping (MXKeyVerificationTransaction) -> Void, - failure: @escaping (Error) -> Void + failure: @escaping (Swift.Error) -> Void ) { Task { do { - let sas = try await verification.beginSasVerification(userId: request.otherUser, flowId: request.requestId) - let transaction = MXSASTransactionV2( - sas: sas, - getEmojisAction: { [weak self] in - self?.getEmojis(sas: $0) ?? [] - }, - confirmMatchAction: { [weak self] in - self?.confirm(transaction: $0) - }, - cancelAction: { [weak self] in - self?.cancel(transaction: $0, code: $1) - } - ) - transactions.append(transaction) + let sas = try await verification.startSasVerification(userId: request.otherUser, flowId: request.requestId) + let transaction = addSasTransaction(for: sas, transport: request.transport) await MainActor.run { success(transaction) @@ -158,25 +184,24 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { } } + override var pendingRequests: [MXKeyVerificationRequest] { + return Array(activeRequests.values) + } + override func transactions(_ complete: @escaping ([MXKeyVerificationTransaction]) -> Void) { - complete(transactions) + complete(Array(activeTransactions.values)) } override func keyVerification( fromKeyVerificationEvent event: MXEvent, success: @escaping (MXKeyVerification) -> Void, - failure: @escaping (Error) -> Void + failure: @escaping (Swift.Error) -> Void ) -> MXHTTPOperation? { log.debug("Not implemented") success(MXKeyVerification()) return MXHTTPOperation() } - - override func keyVerificationId(fromDMEvent event: MXEvent) -> String? { - log.debug("Not implemented") - return nil - } - + override func qrCodeTransaction(withTransactionId transactionId: String) -> MXQRCodeTransaction? { log.debug("Not implemented") return nil @@ -186,52 +211,95 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { log.debug("Not implemented") } - override func notifyOthersOfAcceptance(withTransactionId transactionId: String, acceptedUserId: String, acceptedDeviceId: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { + override func notifyOthersOfAcceptance(withTransactionId transactionId: String, acceptedUserId: String, acceptedDeviceId: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) { log.debug("Not implemented") success() } - private func getEmojis(sas: Sas) -> [MXEmojiRepresentation] { - do { - let indices = try verification.emojiIndexes(sas: sas) - let emojis = MXDefaultSASTransaction.allEmojiRepresentations() - return indices.compactMap { idx in - idx < emojis.count ? emojis[idx] : nil - } - } catch { - log.error("Cannot get emoji indices", context: error) - return [] + // MARK: - Private + + func incomingVerificationRequest(userId: String, flowId: String) { + guard let request = verification.verificationRequest(userId: userId, flowId: flowId) else { + log.error("Verification request is not known", context: [ + "flow_id": flowId + ]) + return } + + _ = addRequest(for: request, transport: .toDevice, notify: true) } - private func cancel(request: MXKeyVerificationRequest, code: MXTransactionCancelCode) { - Task { - do { - try await verification.cancelVerification(userId: request.otherUser, flowId: request.requestId, cancelCode: code.value) - } catch { - log.error("Cannot cancel request", context: error) - } + func incomingVerificationStart(userId: String, flowId: String) { + guard let verif = verification.verification(userId: userId, flowId: flowId) else { + log.error("Verification is not known", context: [ + "flow_id": flowId + ]) + return + } + + switch verif { + case .sasV1(let sas): + let transaction = addSasTransaction(for: sas, transport: .toDevice) + transaction.accept() + + case .qrCodeV1: + assertionFailure("Not implemented") } } - private func confirm(transaction: MXSASTransaction) { - Task { - do { - try await verification.confirmVerification(userId: transaction.otherUserId, flowId: transaction.transactionId) - } catch { - log.error("Cannot confirm transaction", context: error) + func updatePendingVerification() { + for request in activeRequests.values { + switch request.processUpdates() { + case .noUpdates: + break + case .updated: + NotificationCenter.default.post(name: .MXKeyVerificationRequestDidChange, object: request) + case .removed: + activeRequests[request.requestId] = nil + } + } + + for transaction in activeTransactions.values { + switch transaction.processUpdates() { + case .noUpdates: + break + case .updated: + NotificationCenter.default.post(name: .MXKeyVerificationTransactionDidChange, object: transaction) + case .removed: + activeTransactions[transaction.transactionId] = nil } } } - private func cancel(transaction: MXSASTransaction, code: MXTransactionCancelCode) { - Task { - do { - try await verification.cancelVerification(userId: transaction.otherUserId, flowId: transaction.transactionId, cancelCode: code.value) - } catch { - log.error("Cannot cancel request", context: error) - } + private func addRequest( + for request: VerificationRequest, + transport: MXKeyVerificationTransport, + notify: Bool = false + ) -> MXKeyVerificationRequestV2 { + + let request = MXKeyVerificationRequestV2( + request: request, + transport: transport, + handler: verification + ) + activeRequests[request.requestId] = request + + if notify { + NotificationCenter.default.post( + name: .MXKeyVerificationManagerNewRequest, + object: self, + userInfo: [ + MXKeyVerificationManagerNotificationRequestKey: request + ] + ) } + return request + } + + private func addSasTransaction(for sas: Sas, transport: MXKeyVerificationTransport) -> MXSASTransactionV2 { + let transaction = MXSASTransactionV2(sas: sas, transport: transport, handler: verification) + activeTransactions[transaction.transactionId] = transaction + return transaction } } diff --git a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift index 644f7eb57d..d290b095c5 100644 --- a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift +++ b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift @@ -21,9 +21,8 @@ import Foundation import MatrixSDKCrypto /// Verification request originating from `MatrixSDKCrypto` +@available(iOS 13.0.0, *) class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { - typealias CancelAction = (MXKeyVerificationRequest, MXTransactionCancelCode) -> Void - var state: MXKeyVerificationRequestState { // State as enum will be moved to MatrixSDKCrypto in the future // to avoid the mapping of booleans into state @@ -50,24 +49,18 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { } var isFromMyUser: Bool { - return request.weStarted + return otherUser == handler.userId } var isFromMyDevice: Bool { - // Not exposed on the underlying request, - // assuming that if request is from us, it is from our devide - log.debug("Not fully implemented") - return isFromMyUser + return request.weStarted } var requestId: String { return request.flowId } - var transport: MXKeyVerificationTransport { - log.debug("Not fully implemented") - return .directMessage - } + let transport: MXKeyVerificationTransport var otherUser: String { return request.otherUserId @@ -78,7 +71,7 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { } var methods: [String] { - return (isFromMyUser ? myMethods : otherMethods) ?? [] + return (isFromMyDevice ? myMethods : otherMethods) ?? [] } var myMethods: [String]? { @@ -90,21 +83,26 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { } private var request: VerificationRequest - private let cancelAction: CancelAction + private let handler: MXCryptoVerificationRequesting private let log = MXNamedLog(name: "MXKeyVerificationRequestV2") - init(request: VerificationRequest, cancelAction: @escaping CancelAction) { + init(request: VerificationRequest, transport: MXKeyVerificationTransport, handler: MXCryptoVerificationRequesting) { self.request = request - self.cancelAction = cancelAction + self.transport = transport + self.handler = handler } - func update(request: VerificationRequest) { + func processUpdates() -> MXKeyVerificationUpdateResult { + guard let request = handler.verificationRequest(userId: otherUser, flowId: requestId) else { + return .removed + } + guard self.request != request else { - return + return .noUpdates } self.request = request - NotificationCenter.default.post(name: .MXKeyVerificationRequestDidChange, object: self) + return .updated } func accept( @@ -112,7 +110,22 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { success: @escaping () -> Void, failure: @escaping (Error) -> Void ) { - log.debug("Not implemented") + Task { + do { + try await handler.acceptVerificationRequest( + userId: otherUser, + flowId: requestId, + methods: methods + ) + await MainActor.run { + success() + } + } catch { + await MainActor.run { + failure(error) + } + } + } } func cancel( @@ -120,8 +133,22 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { success: (() -> Void)?, failure: ((Error) -> Void)? = nil ) { - cancelAction(self, code) - success?() + Task { + do { + try await handler.cancelVerification( + userId: otherUser, + flowId: requestId, + cancelCode: code.value + ) + await MainActor.run { + success?() + } + } catch { + await MainActor.run { + failure?(error) + } + } + } } } diff --git a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift index fea7b33e86..a0ffafd0c3 100644 --- a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift +++ b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift @@ -21,10 +21,8 @@ import Foundation import MatrixSDKCrypto /// SAS transaction originating from `MatrixSDKCrypto` +@available(iOS 13.0.0, *) class MXSASTransactionV2: NSObject, MXSASTransaction { - typealias GetEmojisAction = (Sas) -> [MXEmojiRepresentation] - typealias ConfirmMatchAction = (MXSASTransaction) -> Void - typealias CancelAction = (MXSASTransaction, MXTransactionCancelCode) -> Void var state: MXSASTransactionState { // State as enum will be moved to MatrixSDKCrypto in the future @@ -44,7 +42,16 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { } var sasEmoji: [MXEmojiRepresentation]? { - return getEmojisAction(sas) + do { + let indices = try handler.emojiIndexes(sas: sas) + let emojis = MXDefaultSASTransaction.allEmojiRepresentations() + return indices.compactMap { idx in + idx < emojis.count ? emojis[idx] : nil + } + } catch { + log.error("Cannot get emoji indices", context: error) + return nil + } } var sasDecimal: String? { @@ -56,10 +63,7 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { return sas.flowId } - var transport: MXKeyVerificationTransport { - log.debug("Not fully implemented") - return .directMessage - } + let transport: MXKeyVerificationTransport var isIncoming: Bool { return !sas.weStarted @@ -96,43 +100,72 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { } private var sas: Sas - private let getEmojisAction: GetEmojisAction - private let confirmMatchAction: ConfirmMatchAction - private let cancelAction: CancelAction - + private let handler: MXCryptoSASVerifying + private let log = MXNamedLog(name: "MXSASTransactionV2") - init( - sas: Sas, - getEmojisAction: @escaping GetEmojisAction, - confirmMatchAction: @escaping ConfirmMatchAction, - cancelAction: @escaping CancelAction - ) { + init(sas: Sas, transport: MXKeyVerificationTransport, handler: MXCryptoSASVerifying) { self.sas = sas - self.getEmojisAction = getEmojisAction - self.confirmMatchAction = confirmMatchAction - self.cancelAction = cancelAction + self.transport = transport + self.handler = handler } - func update(sas: Sas) { + func processUpdates() -> MXKeyVerificationUpdateResult { + guard + let verification = handler.verification(userId: otherUserId, flowId: transactionId), + case .sasV1(let sas) = verification + else { + return .removed + } + guard self.sas != sas else { - return + return .noUpdates } self.sas = sas - NotificationCenter.default.post(name: .MXKeyVerificationTransactionDidChange, object: self) + return .updated + } + + func accept() { + Task { + do { + try await handler.acceptSasVerification(userId: otherUserId, flowId: transactionId) + } catch { + log.error("Cannot accept transaction", context: error) + } + } } func confirmSASMatch() { - confirmMatchAction(self) + Task { + do { + try await handler.confirmVerification(userId: otherUserId, flowId: transactionId) + } catch { + log.error("Cannot confirm transaction", context: error) + } + } } func cancel(with code: MXTransactionCancelCode) { - cancelAction(self, code) + cancel(with: code) { + // No-op + } failure: { [weak self] in + self?.log.error("Cannot cancel transaction", context: $0) + } } func cancel(with code: MXTransactionCancelCode, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { - cancelAction(self, code) - success() + Task { + do { + try await handler.cancelVerification(userId: otherUserId, flowId: transactionId, cancelCode: code.value) + await MainActor.run { + success() + } + } catch { + await MainActor.run { + failure(error) + } + } + } } } diff --git a/MatrixSDK/MXSDKOptions.h b/MatrixSDK/MXSDKOptions.h index 6f7c1b8c4b..ce604e4f5b 100644 --- a/MatrixSDK/MXSDKOptions.h +++ b/MatrixSDK/MXSDKOptions.h @@ -216,6 +216,14 @@ NS_ASSUME_NONNULL_BEGIN #endif +/** + Enable performance optimization where inbound group sessions are cached between decryption of events + rather than fetched from the store every time. + + @remark By default, the value is set randomly between YES / NO to perform a very basic A/B test + */ +@property (nonatomic) BOOL enableGroupSessionCache; + /** Enable symmetric room key backups diff --git a/MatrixSDK/MXSDKOptions.m b/MatrixSDK/MXSDKOptions.m index fd2a31b7ae..6c3c712905 100644 --- a/MatrixSDK/MXSDKOptions.m +++ b/MatrixSDK/MXSDKOptions.m @@ -59,6 +59,10 @@ - (instancetype)init _enableCryptoV2 = NO; #endif + // The value is set randomly between YES / NO to perform a very basic A/B test + // measured by `analytics` (if set and enabled) + _enableGroupSessionCache = arc4random_uniform(2) == 1; + _enableSymmetricBackup = NO; } diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 550282d7d2..0d447a5b08 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -195,6 +195,10 @@ Queue of requested direct room change operations ([MXSession setRoom:directWithU */ @property (nonatomic) NSUInteger preventPauseCount; +#if TARGET_OS_IPHONE +@property (nonatomic, strong, readonly) MXUIKitApplicationStateService *applicationStateService; +#endif + @property (nonatomic, readwrite) MXScanManager *scanManager; /** @@ -233,6 +237,9 @@ - (id)initWithMatrixRestClient:(MXRestClient*)mxRestClient _accountData = [[MXAccountData alloc] init]; peekingRooms = [NSMutableArray array]; _preventPauseCount = 0; +#if TARGET_OS_IPHONE + _applicationStateService = [MXUIKitApplicationStateService new]; +#endif directRoomsOperationsQueue = [NSMutableArray array]; publicisedGroupsByUserId = [[NSMutableDictionary alloc] init]; nativeToVirtualRoomIds = [NSMutableDictionary dictionary]; @@ -1373,6 +1380,17 @@ - (void)setPreventPauseCount:(NSUInteger)preventPauseCount MXLogDebug(@"[MXSession] setPreventPauseCount: Actually pause the session"); [self pause]; } + else + { +#if TARGET_OS_IPHONE + // Pause the session if app is already in the background/inactive but pause wasn't requested + if (self.applicationStateService.applicationState != UIApplicationStateActive) + { + MXLogDebug(@"[MXSession] setPreventPauseCount: Pause session on already backgrounded/inactive app"); + [self pause]; + } +#endif + } } } @@ -1388,6 +1406,7 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout dispatch_group_t initialSyncDispatchGroup = dispatch_group_create(); __block MXTaskProfile *syncTaskProfile; + __block StopDurationTracking stopDurationTracking; __block MXSyncResponse *syncResponse; __block BOOL useLiveResponse = YES; @@ -1419,6 +1438,13 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout BOOL isInitialSync = !self.isEventStreamInitialised; MXTaskProfileName taskName = isInitialSync ? MXTaskProfileNameStartupInitialSync : MXTaskProfileNameStartupIncrementalSync; syncTaskProfile = [MXSDKOptions.sharedInstance.profiler startMeasuringTaskWithName:taskName]; + if (isInitialSync) { + // Temporarily tracking performance both by `MXSDKOptions.sharedInstance.profiler` (manually measuring time) + // and `MXSDKOptions.sharedInstance.analyticsDelegate` (delegating to performance monitoring tool). + // This ambiguity will be resolved in the future + NSString *operation = MXSDKOptions.sharedInstance.enableGroupSessionCache ? @"initialSync.enableGroupSessionCache" : @"initialSync.diableGroupSessionCache"; + stopDurationTracking = [MXSDKOptions.sharedInstance.analyticsDelegate startDurationTrackingForName:@"MXSession" operation:operation]; + } } NSString * streamToken = self.store.eventStreamToken; @@ -1461,6 +1487,9 @@ - (void)serverSyncWithServerTimeout:(NSUInteger)serverTimeout syncTaskProfile.units = syncResponse.rooms.join.count; [MXSDKOptions.sharedInstance.profiler stopMeasuringTaskWithProfile:syncTaskProfile]; + if (stopDurationTracking) { + stopDurationTracking(); + } } BOOL isInitialSync = !self.isEventStreamInitialised; diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index b2308292df..c39fa05d3a 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -156,6 +156,7 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXLoginSSOIdentityProviderBrand.h" // Bridging to Swift +#import "MXCryptoConstants.h" #import "MXCryptoStore.h" #import "MXRealmCryptoStore.h" #import "MXCryptoAlgorithms.h" @@ -193,3 +194,4 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXBeaconInfo.h" #import "MXBeacon.h" #import "MXEventAssetType.h" + diff --git a/MatrixSDK/MatrixSDKVersion.m b/MatrixSDK/MatrixSDKVersion.m index 6f5561e6df..1acda7f4a7 100644 --- a/MatrixSDK/MatrixSDKVersion.m +++ b/MatrixSDK/MatrixSDKVersion.m @@ -16,4 +16,4 @@ #import -NSString *const MatrixSDKVersion = @"0.23.17"; +NSString *const MatrixSDKVersion = @"0.23.18"; diff --git a/MatrixSDK/Utils/Logs/MXLog.swift b/MatrixSDK/Utils/Logs/MXLog.swift index 50d4cceda1..4a2d75478c 100644 --- a/MatrixSDK/Utils/Logs/MXLog.swift +++ b/MatrixSDK/Utils/Logs/MXLog.swift @@ -19,12 +19,11 @@ import SwiftyBeaver /// Various MXLog configuration options. Used in conjunction with `MXLog.configure()` @objc public class MXLogConfiguration: NSObject { - /// the desired log level. `.verbose` by default. - @objc public var logLevel: MXLogLevel = MXLogLevel.verbose + @objc public var logLevel = MXLogLevel.verbose /// whether logs should be written directly to files. `false` by default. - @objc public var redirectLogsToFiles: Bool = false + @objc public var redirectLogsToFiles = false /// the maximum total space to use for log files in bytes. `100MB` by default. @objc public var logFilesSizeLimit: UInt = 100 * 1024 * 1024 // 100MB @@ -33,7 +32,7 @@ import SwiftyBeaver @objc public var maxLogFilesCount: UInt = 50 /// the subname for log files. Files will be named as 'console-[subLogName].log'. `nil` by default - @objc public var subLogName: String? = nil + @objc public var subLogName: String? } /// MXLog logging levels. Use .none to disable logging entirely. @@ -48,7 +47,7 @@ import SwiftyBeaver private var logger: SwiftyBeaver.Type = { let logger = SwiftyBeaver.self - MXLog.configureLogger(logger, withConfiguration:MXLogConfiguration()) + MXLog.configureLogger(logger, withConfiguration: MXLogConfiguration()) return logger }() @@ -58,18 +57,20 @@ private var logger: SwiftyBeaver.Type = { Please see `MXLog.h` for Objective-C options. */ @objc public class MXLog: NSObject { - /// Method used to customize MXLog's behavior. /// Called automatically when first accessing the logger with the default values. /// Please see `MXLogConfiguration` for all available options. /// - Parameters: /// - configuration: the `MXLogConfiguration` instance to use - @objc static public func configure(_ configuration: MXLogConfiguration) { + @objc public static func configure(_ configuration: MXLogConfiguration) { configureLogger(logger, withConfiguration: configuration) } - public static func verbose(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { + public static func verbose(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.verbose(message(), file, function, line: line, context: context) } @@ -78,8 +79,11 @@ private var logger: SwiftyBeaver.Type = { logger.verbose(message, file, function, line: line) } - public static func debug(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { + public static func debug(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.debug(message(), file, function, line: line, context: context) } @@ -88,8 +92,11 @@ private var logger: SwiftyBeaver.Type = { logger.debug(message, file, function, line: line) } - public static func info(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { + public static func info(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.info(message(), file, function, line: line, context: context) } @@ -98,8 +105,11 @@ private var logger: SwiftyBeaver.Type = { logger.info(message, file, function, line: line) } - public static func warning(_ message: @autoclosure () -> Any, _ - file: String = #file, _ function: String = #function, line: Int = #line, context: Any? = nil) { + public static func warning(_ message: @autoclosure () -> Any, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.warning(message(), file, function, line: line, context: context) } @@ -113,24 +123,20 @@ private var logger: SwiftyBeaver.Type = { /// - Parameters: /// - message: Description of the error without any variables (this is to improve error aggregations by type) /// - context: Additional context-dependent details about the issue - public static func error( - _ message: StaticString, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil - ) { + public static func error(_ message: StaticString, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.error(message, file, function, line: line, context: context) } @available(swift, obsoleted: 5.4) - @objc public static func logError( - _ message: String, - file: String, - function: String, - line: Int, - context: Any? = nil - ) { + @objc public static func logError(_ message: String, + file: String, + function: String, + line: Int, + context: Any? = nil) { logger.error(message, file, function, line: line, context: context) } @@ -142,13 +148,11 @@ private var logger: SwiftyBeaver.Type = { /// - Parameters: /// - message: Description of the error without any variables (this is to improve error aggregations by type) /// - context: Additional context-dependent details about the issue - public static func failure( - _ message: StaticString, - _ file: String = #file, - _ function: String = #function, - line: Int = #line, - context: Any? = nil - ) { + public static func failure(_ message: StaticString, + _ file: String = #file, + _ function: String = #function, + line: Int = #line, + context: Any? = nil) { logger.error(message, file, function, line: line, context: context) #if DEBUG assertionFailure("\(message)") @@ -156,13 +160,11 @@ private var logger: SwiftyBeaver.Type = { } @available(swift, obsoleted: 5.4) - @objc public static func logFailure( - _ message: String, - file: String, - function: String, - line: Int, - context: Any? = nil - ) { + @objc public static func logFailure(_ message: String, + file: String, + function: String, + line: Int, + context: Any? = nil) { logger.error(message, file, function, line: line, context: context) #if DEBUG assertionFailure("\(message)") @@ -190,26 +192,26 @@ private var logger: SwiftyBeaver.Type = { let consoleDestination = ConsoleDestination() consoleDestination.useNSLog = true consoleDestination.asynchronously = false - consoleDestination.format = "$C $M $X" // Format is `Color Message Context`, see https://docs.swiftybeaver.com/article/20-custom-format + consoleDestination.format = "$C$M $X$c" // Format is `Color Message Context`, see https://docs.swiftybeaver.com/article/20-custom-format consoleDestination.levelColor.verbose = "" consoleDestination.levelColor.debug = "" consoleDestination.levelColor.info = "" - consoleDestination.levelColor.warning = "⚠️" - consoleDestination.levelColor.error = "🚨" + consoleDestination.levelColor.warning = "⚠️ " + consoleDestination.levelColor.error = "🚨 " switch configuration.logLevel { - case .verbose: - consoleDestination.minLevel = .verbose - case .debug: - consoleDestination.minLevel = .debug - case .info: - consoleDestination.minLevel = .info - case .warning: - consoleDestination.minLevel = .warning - case .error: - consoleDestination.minLevel = .error - case .none: - break + case .verbose: + consoleDestination.minLevel = .verbose + case .debug: + consoleDestination.minLevel = .debug + case .info: + consoleDestination.minLevel = .info + case .warning: + consoleDestination.minLevel = .warning + case .error: + consoleDestination.minLevel = .error + case .none: + break } logger.addDestination(consoleDestination) @@ -244,6 +246,6 @@ struct MXNamedLog { } private func formattedMessage(_ message: Any, function: String) -> String { - return "[\(name)] \(function): \(message)" + "[\(name)] \(function): \(message)" } } diff --git a/MatrixSDK/Utils/MXAnalyticsDelegate.h b/MatrixSDK/Utils/MXAnalyticsDelegate.h index d63b92135a..ddc73d8fe7 100644 --- a/MatrixSDK/Utils/MXAnalyticsDelegate.h +++ b/MatrixSDK/Utils/MXAnalyticsDelegate.h @@ -20,6 +20,12 @@ #import "MXCallHangupEventContent.h" #import "MXTaskProfile.h" +/** + Callback function to stop ongoing duration tracking + started by `[MXAnalyticsDelegate startDurationTracking]` + */ +typedef void (^StopDurationTracking)(void); + NS_ASSUME_NONNULL_BEGIN /** @@ -46,6 +52,21 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)trackDuration:(NSInteger)milliseconds name:(MXTaskProfileName)name units:(NSUInteger)units; +/** + Start tracking the duration of a task and manually stop when finished using the return handle + + @note This method is similar to `trackDuration`, but instead of passing the measured duraction + as a parameter, it relies on the implementation of `MXAnalyticsDelegate` to perform the + measurements. + + @param name Name of the entity being measured (e.g. `RoomsViewController` or `Crypto`) + @param operation Short code identifying the type of operation measured (e.g. `viewDidLoad` or `decrypt`) + + + @return Handle that can be used to stop the performance tracking + */ +- (StopDurationTracking)startDurationTrackingForName:(NSString *)name operation:(NSString *)operation; + /** Report that a call has started. diff --git a/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift new file mode 100644 index 0000000000..94868f3fec --- /dev/null +++ b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift @@ -0,0 +1,89 @@ +// +// Copyright 2022 The Matrix.org Foundation C.I.C +// +// 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. +// + +import Foundation +import XCTest +@testable import MatrixSDK + +#if os(iOS) + +import MatrixSDKCrypto + +@available(iOS 13.0.0, *) +class MXCrossSigningV2UnitTests: XCTestCase { + + var crypto: CryptoCrossSigningStub! + var crossSigning: MXCrossSigningV2! + var restClient: MXRestClientStub! + + override func setUp() { + crypto = CryptoCrossSigningStub() + restClient = MXRestClientStub() + crossSigning = MXCrossSigningV2( + crossSigning: crypto, + restClient: restClient + ) + } + + func test_state_notBootstrapped() { + XCTAssertEqual(crossSigning.state, .notBootstrapped) + } + + func test_state_canCrossSign() { + crypto.stubbedStatus = CrossSigningStatus(hasMaster: true, hasSelfSigning: true, hasUserSigning: true) + XCTAssertEqual(crossSigning.state, .canCrossSign) + } + + func test_state_crossSigningExists() { + let exp = expectation(description: "exp") + crypto.stubbedVerifiedUsers = [] + crypto.stubbedIdentities = [ + "Alice": .own( + userId: "Alice", + trustsOurOwnDevice: true, + masterKey: "", + selfSigningKey: "", + userSigningKey: "" + ) + ] + crossSigning.refreshState { _ in + XCTAssertEqual(self.crossSigning.state, .crossSigningExists) + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func test_state_trustCrossSigning() { + let exp = expectation(description: "exp") + crypto.stubbedVerifiedUsers = ["Alice"] + crypto.stubbedIdentities = [ + "Alice": .own( + userId: "Alice", + trustsOurOwnDevice: true, + masterKey: "", + selfSigningKey: "", + userSigningKey: "" + ) + ] + crossSigning.refreshState { _ in + XCTAssertEqual(self.crossSigning.state, .trustCrossSigning) + exp.fulfill() + } + waitForExpectations(timeout: 1) + } +} + +#endif diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift index 1e90360acf..5414778e37 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift @@ -21,7 +21,10 @@ import Foundation import MatrixSDKCrypto -class DevicesSourceStub: MXCryptoDevicesSource { +class CryptoIdentityStub: MXCryptoIdentity { + var userId: String = "Alice" + var deviceId: String = "ABCD" + var deviceCurve25519Key: String? { return nil } @@ -29,7 +32,9 @@ class DevicesSourceStub: MXCryptoDevicesSource { var deviceEd25519Key: String? { return nil } - +} + +class DevicesSourceStub: CryptoIdentityStub, MXCryptoDevicesSource { var devices = [String: [String: Device]]() func device(userId: String, deviceId: String) -> Device? { @@ -41,7 +46,8 @@ class DevicesSourceStub: MXCryptoDevicesSource { } } -class UserIdentitySourceStub: MXCryptoUserIdentitySource { +@available(iOS 13.0.0, *) +class UserIdentitySourceStub: CryptoIdentityStub, MXCryptoUserIdentitySource { var identities = [String: UserIdentity]() func userIdentity(userId: String) -> UserIdentity? { return identities[userId] @@ -51,6 +57,97 @@ class UserIdentitySourceStub: MXCryptoUserIdentitySource { func isUserVerified(userId: String) -> Bool { return verification[userId] ?? false } + + func downloadKeys(users: [String]) async throws { + + } +} + +@available(iOS 13.0.0, *) +class CryptoCrossSigningStub: CryptoIdentityStub, MXCryptoCrossSigning { + var stubbedStatus = CrossSigningStatus( + hasMaster: false, + hasSelfSigning: false, + hasUserSigning: false + ) + func crossSigningStatus() -> CrossSigningStatus { + return stubbedStatus + } + + func bootstrapCrossSigning(authParams: [AnyHashable : Any]) async throws { + } + + var stubbedIdentities = [String: UserIdentity]() + func userIdentity(userId: String) -> UserIdentity? { + stubbedIdentities[userId] + } + + var stubbedVerifiedUsers = Set() + func isUserVerified(userId: String) -> Bool { + return stubbedVerifiedUsers.contains(userId) + } + + func downloadKeys(users: [String]) async throws { + } +} + +@available(iOS 13.0.0, *) +class CryptoVerificationStub: CryptoIdentityStub { + var stubbedRequests = [String: VerificationRequest]() + var stubbedTransactions = [String: Verification]() + var stubbedErrors = [String: Error]() + var stubbedEmojis = [String: [Int]]() +} + +@available(iOS 13.0.0, *) +extension CryptoVerificationStub: MXCryptoVerificationRequesting { + func requestSelfVerification(methods: [String]) async throws -> VerificationRequest { + .stub() + } + + func requestVerification(userId: String, roomId: String, methods: [String]) async throws -> VerificationRequest { + .stub() + } + + func verificationRequest(userId: String, flowId: String) -> VerificationRequest? { + return stubbedRequests[flowId] + } + + func acceptVerificationRequest(userId: String, flowId: String, methods: [String]) async throws { + if let error = stubbedErrors[flowId] { + throw error + } + } + + func cancelVerification(userId: String, flowId: String, cancelCode: String) async throws { + if let error = stubbedErrors[flowId] { + throw error + } + } +} + +@available(iOS 13.0.0, *) +extension CryptoVerificationStub: MXCryptoVerifying { + func verification(userId: String, flowId: String) -> Verification? { + return stubbedTransactions[flowId] + } + + func confirmVerification(userId: String, flowId: String) async throws { + } +} + +@available(iOS 13.0.0, *) +extension CryptoVerificationStub: MXCryptoSASVerifying { + func startSasVerification(userId: String, flowId: String) async throws -> Sas { + .stub() + } + + func acceptSasVerification(userId: String, flowId: String) async throws { + } + + func emojiIndexes(sas: Sas) throws -> [Int] { + stubbedEmojis[sas.flowId] ?? [] + } } #endif diff --git a/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift b/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift index 299bc03223..87eb924f91 100644 --- a/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift +++ b/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift @@ -24,31 +24,11 @@ import MatrixSDKCrypto @available(iOS 13.0.0, *) class MXDeviceInfoSourceUnitTests: XCTestCase { - class SourceStub: MXCryptoDevicesSource { - var deviceCurve25519Key: String? { - return nil - } - - var deviceEd25519Key: String? { - return nil - } - - var devices = [String: [String: Device]]() - - func device(userId: String, deviceId: String) -> Device? { - return devices[userId]?[deviceId] - } - - func devices(userId: String) -> [Device] { - return devices[userId]?.map { $0.value } ?? [] - } - } - - var cryptoSource: SourceStub! + var cryptoSource: DevicesSourceStub! var source: MXDeviceInfoSource! override func setUp() { - cryptoSource = SourceStub() + cryptoSource = DevicesSourceStub() source = MXDeviceInfoSource(source: cryptoSource) } diff --git a/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift b/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift index 7e9a6d8003..bb1d9ffc9b 100644 --- a/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift @@ -22,10 +22,31 @@ import XCTest import MatrixSDKCrypto @testable import MatrixSDK +@available(iOS 13.0.0, *) class MXKeyVerificationRequestV2UnitTests: XCTestCase { + enum Error: Swift.Error, Equatable { + case dummy + } + + var verification: CryptoVerificationStub! + + override func setUp() { + verification = CryptoVerificationStub() + } + + func makeRequest(for request: VerificationRequest = .stub()) -> MXKeyVerificationRequestV2 { + .init( + request: request, + transport: .directMessage, + handler: verification + ) + } + + // MARK: - Test Properties + func test_usesCorrectProperties() { let stub = VerificationRequest.stub( - otherUserId: "Bob", + otherUserId: "Alice", otherDeviceId: "Device2", flowId: "123", weStarted: true, @@ -33,22 +54,51 @@ class MXKeyVerificationRequestV2UnitTests: XCTestCase { ourMethods: ["sas", "unknown"] ) - let request = MXKeyVerificationRequestV2( - request: stub, - cancelAction: { _, _ in } - ) + let request = makeRequest(for: stub) XCTAssertTrue(request.isFromMyUser) XCTAssertTrue(request.isFromMyDevice) XCTAssertEqual(request.requestId, "123") XCTAssertEqual(request.transport, MXKeyVerificationTransport.directMessage) - XCTAssertEqual(request.otherUser, "Bob") + XCTAssertEqual(request.otherUser, "Alice") XCTAssertEqual(request.otherDevice, "Device2") XCTAssertEqual(request.methods, ["sas", "unknown"]) XCTAssertEqual(request.myMethods, ["sas", "unknown"]) XCTAssertEqual(request.otherMethods, ["sas", "qr"]) } + func test_isFromMyUser_ifUsersMatch() { + verification.userId = "Alice" + let request1 = makeRequest(for: .stub( + otherUserId: "Alice" + )) + XCTAssertTrue(request1.isFromMyUser) + + let request2 = makeRequest(for: .stub( + otherUserId: "Bob" + )) + XCTAssertFalse(request2.isFromMyUser) + } + + func test_methodsForWhoStarted() { + let ourMethods = ["A", "B"] + let theirMethods = ["C", "D"] + + let request1 = makeRequest(for: .stub( + weStarted: true, + theirMethods: theirMethods, + ourMethods: ourMethods + )) + XCTAssertEqual(request1.methods, ourMethods) + + let request2 = makeRequest(for: .stub( + weStarted: false, + theirMethods: theirMethods, + ourMethods: ourMethods + )) + XCTAssertEqual(request2.methods, theirMethods) + } + func test_state() { let testCases: [(VerificationRequest, MXKeyVerificationRequestState)] = [ (.stub( @@ -96,14 +146,11 @@ class MXKeyVerificationRequestV2UnitTests: XCTestCase { ] for (stub, state) in testCases { - let request = MXKeyVerificationRequestV2( - request: stub, - cancelAction: { _, _ in } - ) + let request = makeRequest(for: stub) XCTAssertEqual(request.state, state) } } - + func test_reasonCancelCode() { let cancelInfo = CancelInfo( cancelCode: "123", @@ -111,27 +158,113 @@ class MXKeyVerificationRequestV2UnitTests: XCTestCase { cancelledByUs: true ) - let request = MXKeyVerificationRequestV2( - request: .stub(cancelInfo: cancelInfo), - cancelAction: { _, _ in } - ) - + let request = makeRequest(for: .stub(cancelInfo: cancelInfo)) + XCTAssertEqual(request.reasonCancelCode?.value, "123") XCTAssertEqual(request.reasonCancelCode?.humanReadable, "Changed mind") } - func test_update_postsNotification_ifChanged() { - let exp = expectation(description: "exp") - let request = MXKeyVerificationRequestV2( - request: .stub(isReady: false), - cancelAction: { _, _ in } + // MARK: - Test Updates + + func test_processUpdated_removedIfNoMatchingRequest() { + verification.stubbedRequests = [:] + let request = makeRequest() + + let result = request.processUpdates() + + XCTAssertEqual(result, MXKeyVerificationUpdateResult.removed) + } + + func test_processUpdated_noUpdatesIfRequestUnchanged() { + let stub = VerificationRequest.stub( + flowId: "ABC", + isReady: false ) - NotificationCenter.default.addObserver(forName: .MXKeyVerificationRequestDidChange, object: request, queue: OperationQueue.main) { notif in - XCTAssertEqual(request.state, MXKeyVerificationRequestStateReady) + verification.stubbedRequests = [stub.flowId: stub] + let request = makeRequest(for: stub) + + let result = request.processUpdates() + + XCTAssertEqual(result, MXKeyVerificationUpdateResult.noUpdates) + } + + func test_processUpdated_updatedIfRequestChanged() { + let stub = VerificationRequest.stub( + flowId: "ABC", + isReady: false + ) + verification.stubbedRequests = [stub.flowId: stub] + let request = makeRequest(for: stub) + verification.stubbedRequests = [stub.flowId: .stub( + flowId: "ABC", + isReady: true + )] + + let result = request.processUpdates() + + XCTAssertEqual(result, MXKeyVerificationUpdateResult.updated) + XCTAssertEqual(request.state, MXKeyVerificationRequestStateReady) + } + + // MARK: - Test Interactions + + func test_acceptSucceeds() { + let exp = expectation(description: "exp") + verification.stubbedErrors = [:] + let request = makeRequest(for: .stub(flowId: "ABC")) + + request.accept(withMethods: []) { + exp.fulfill() + XCTAssert(true) + } failure: { _ in + XCTFail("Accepting should not fail") + } + + waitForExpectations(timeout: 1) + } + + func test_acceptFails() { + let exp = expectation(description: "exp") + verification.stubbedErrors = ["ABC": Error.dummy] + let request = makeRequest(for: .stub(flowId: "ABC")) + + request.accept(withMethods: []) { + XCTFail("Accepting should not succeed") + } failure: { error in exp.fulfill() + XCTAssertEqual(error as? Error, Error.dummy) } - request.update(request: .stub(isReady: true)) + waitForExpectations(timeout: 1) + } + + func test_cancelSucceeds() { + let exp = expectation(description: "exp") + verification.stubbedErrors = [:] + let request = makeRequest(for: .stub(flowId: "ABC")) + + request.cancel(with: MXTransactionCancelCode()) { + exp.fulfill() + XCTAssert(true) + } failure: { _ in + XCTFail("Cancelling should not fail") + } + + waitForExpectations(timeout: 1) + } + + func test_cancelFails() { + let exp = expectation(description: "exp") + verification.stubbedErrors = ["ABC": Error.dummy] + let request = makeRequest(for: .stub(flowId: "ABC")) + + + request.cancel(with: MXTransactionCancelCode()) { + XCTFail("Cancelling should not succeed") + } failure: { error in + exp.fulfill() + XCTAssertEqual(error as? Error, Error.dummy) + } waitForExpectations(timeout: 1) } diff --git a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift index 16c1c76af8..8fb770f565 100644 --- a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift @@ -22,7 +22,23 @@ import XCTest import MatrixSDKCrypto @testable import MatrixSDK +@available(iOS 13.0.0, *) class MXSASTransactionV2UnitTests: XCTestCase { + var verification: CryptoVerificationStub! + override func setUp() { + verification = CryptoVerificationStub() + } + + func makeTransaction(for sas: Sas = .stub()) -> MXSASTransactionV2 { + .init( + sas: sas, + transport: .directMessage, + handler: verification + ) + } + + // MARK: - Test Properties + func test_usesCorrectProperties() { let stub = Sas.stub( otherUserId: "Bob", @@ -33,12 +49,7 @@ class MXSASTransactionV2UnitTests: XCTestCase { supportsEmoji: true ) - let transaction = MXSASTransactionV2( - sas: stub, - getEmojisAction: { _ in [] }, - confirmMatchAction: { _ in }, - cancelAction: { _, _ in } - ) + let transaction = makeTransaction(for: stub) XCTAssertEqual(transaction.transactionId, "123") XCTAssertEqual(transaction.transport, MXKeyVerificationTransport.directMessage) @@ -49,6 +60,22 @@ class MXSASTransactionV2UnitTests: XCTestCase { XCTAssertEqual(transaction.dmEventId, "123") } + func test_sasEmoji() { + // Index-to-emoji mapping specified in + // https://spec.matrix.org/v1.3/client-server-api/#sas-method-emoji + verification.stubbedEmojis = [ + "123": [1, 3, 10, 20] + ] + let expectedEmojis = ["🐱", "🐎", "🐧", "🌙"] + + let transaction = makeTransaction(for: .stub( + flowId: "123" + )) + + let emoji = transaction.sasEmoji?.map { $0.emoji } + XCTAssertEqual(emoji, expectedEmojis) + } + func test_state() { let testCases: [(Sas, MXSASTransactionState)] = [ (.stub( @@ -98,13 +125,24 @@ class MXSASTransactionV2UnitTests: XCTestCase { for (stub, state) in testCases { let transaction = MXSASTransactionV2( sas: stub, - getEmojisAction: { _ in [] }, - confirmMatchAction: { _ in }, - cancelAction: { _, _ in } + transport: .directMessage, + handler: verification ) XCTAssertEqual(transaction.state, state) } } + + func test_isIncomingIfWeStarted() { + let transaction1 = makeTransaction(for: .stub( + weStarted: true + )) + XCTAssertFalse(transaction1.isIncoming) + + let transaction2 = makeTransaction(for: .stub( + weStarted: true + )) + XCTAssertFalse(transaction2.isIncoming) + } func test_reasonCancelCode() { let cancelInfo = CancelInfo( @@ -115,65 +153,54 @@ class MXSASTransactionV2UnitTests: XCTestCase { let transaction = MXSASTransactionV2( sas: .stub(cancelInfo: cancelInfo), - getEmojisAction: { _ in [] }, - confirmMatchAction: { _ in }, - cancelAction: { _, _ in } + transport: .directMessage, + handler: verification ) XCTAssertEqual(transaction.reasonCancelCode?.value, "123") XCTAssertEqual(transaction.reasonCancelCode?.humanReadable, "Changed mind") } - - func test_update_postsNotification_ifChanged() { - let exp = expectation(description: "exp") - let transaction = MXSASTransactionV2( - sas: .stub(isDone: false), - getEmojisAction: { _ in [] }, - confirmMatchAction: { _ in }, - cancelAction: { _, _ in } - ) - NotificationCenter.default.addObserver(forName: .MXKeyVerificationTransactionDidChange, object: transaction, queue: OperationQueue.main) { notif in - XCTAssertEqual(transaction.state, MXSASTransactionStateVerified) - exp.fulfill() - } - - transaction.update(sas: .stub(isDone: true)) - - waitForExpectations(timeout: 1) - } - func test_sasEmoji_picksCorrectEmoji() { - let emoji = [ - MXEmojiRepresentation(emoji: "A", andName: "A"), - MXEmojiRepresentation(emoji: "B", andName: "B"), - MXEmojiRepresentation(emoji: "C", andName: "C"), - ] + // MARK: - Test Updates + + func test_processUpdated_removedIfNoMatchingRequest() { + verification.stubbedTransactions = [:] + let transaction = makeTransaction() - let transaction = MXSASTransactionV2( - sas: .stub(), - getEmojisAction: { _ in emoji }, - confirmMatchAction: { _ in }, - cancelAction: { _, _ in } - ) + let result = transaction.processUpdates() - XCTAssertEqual(transaction.sasEmoji, emoji) + XCTAssertEqual(result, MXKeyVerificationUpdateResult.removed) } - func test_confirmSASMatch() { - let exp = expectation(description: "exp") - let transaction = MXSASTransactionV2( - sas: .stub(), - getEmojisAction: { _ in [] }, - confirmMatchAction: { _ in - XCTAssertTrue(true) - exp.fulfill() - }, - cancelAction: { _, _ in } + func test_processUpdated_noUpdatesIfRequestUnchanged() { + let stub = Sas.stub( + flowId: "ABC", + isDone: false ) + verification.stubbedTransactions = [stub.flowId: .sasV1(sas: stub)] + let transaction = makeTransaction(for: stub) - transaction.confirmSASMatch() + let result = transaction.processUpdates() + + XCTAssertEqual(result, MXKeyVerificationUpdateResult.noUpdates) + } + + func test_processUpdated_updatedIfRequestChanged() { + let stub = Sas.stub( + flowId: "ABC", + isDone: false + ) + verification.stubbedTransactions = [stub.flowId: .sasV1(sas: stub)] + let transaction = makeTransaction(for: stub) + verification.stubbedTransactions = [stub.flowId: .sasV1(sas: .stub( + flowId: "ABC", + isDone: true + ))] - waitForExpectations(timeout: 1) + let result = transaction.processUpdates() + + XCTAssertEqual(result, MXKeyVerificationUpdateResult.updated) + XCTAssertEqual(transaction.state, MXSASTransactionStateVerified) } } diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan index 962842e076..a85e21f057 100644 --- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan @@ -40,6 +40,7 @@ "MXCredentialsUnitTests", "MXCrossSigningInfoSourceUnitTests", "MXCrossSigningInfoUnitTests", + "MXCrossSigningV2UnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan index 54fe76fe10..846fc94957 100644 --- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan @@ -50,6 +50,7 @@ "MXCredentialsUnitTests", "MXCrossSigningInfoSourceUnitTests", "MXCrossSigningInfoUnitTests", + "MXCrossSigningV2UnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", @@ -82,7 +83,7 @@ "MXThreadEventTimelineUnitTests", "MXThreadingServiceUnitTests", "MXToolsUnitTests", - "MXTrustLevelSourceUnitTests", + "MXTrustLevelSourceUnitTests" ], "target" : { "containerPath" : "container:MatrixSDK.xcodeproj", diff --git a/MatrixSDKTests/Utils/MXSessionTracker.swift b/MatrixSDKTests/Utils/MXSessionTracker.swift index 8773370c06..18bb2d23f5 100644 --- a/MatrixSDKTests/Utils/MXSessionTracker.swift +++ b/MatrixSDKTests/Utils/MXSessionTracker.swift @@ -50,12 +50,12 @@ class MXSessionTracker { func printOpenMXSessions() { for (trackId, trackedMXSession) in trackedMXSessions { - MXLog.error("MXSession for user is not closed. It was created from:", context: [ + MXLog.error("MXSession for user is not closed", context: [ "track_id": trackId, - "user_id": trackedMXSession.userDeviceId ]) + MXLog.debug("MXSession was created from:") trackedMXSession.callStack.forEach { call in - MXLog.error(" -", context: call) + MXLog.debug(" - \(call)") } } } diff --git a/README.rst b/README.rst index 750e012879..74dff902cb 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,13 @@ -.. image:: https://codecov.io/gh/matrix-org/matrix-ios-sdk/branch/develop/graph/badge.svg?token=2c9mzJoVpu:target: https://codecov.io/gh/matrix-org/matrix-ios-sdk +.. image:: https://img.shields.io/cocoapods/v/MatrixSDK?style=flat-square + :target: https://github.com/matrix-org/matrix-ios-sdk/releases +.. image:: https://img.shields.io/cocoapods/p/MatrixSDK?style=flat-square + :target: README.rst +.. image:: https://img.shields.io/github/workflow/status/matrix-org/matrix-ios-sdk/Lint%20CI/develop?style=flat-square + :target: https://github.com/matrix-org/matrix-ios-sdk/actions?query=branch%3Adevelop +.. image:: https://codecov.io/gh/matrix-org/matrix-ios-sdk/branch/develop/graph/badge.svg?token=2c9mzJoVpu + :target: https://codecov.io/gh/matrix-org/matrix-ios-sdk +.. image:: https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg?style=flat-square + :target: https://opensource.org/licenses/Apache-2.0 Matrix iOS SDK ==============