CVE-2022-39255 (Swift, published 2022-09-28T21:15Z)
Description: value: Matrix iOS SDK allows developers to build iOS apps compatible with Matrix. Prior to version 0.23.19, an attacker cooperating with a malicious homeserver can construct messages that legitimately appear to have come from another person, without any indication such as a grey shield. Additionally, a sophisticated attacker cooperating with a malicious homeserver could employ this vulnerability to perform a targeted attack in order to send fake to-device messages appearing to originate from another user. This can allow, for example, to inject the key backup secret during a self-verification, to make a targeted device start using a malicious key backup spoofed by the homeserver. These attacks are possible due to a protocol confusion vulnerability that accepts to-device messages encrypted with Megolm instead of Olm. matrix-ios-sdk version 0.23.19 has been modified to only accept Olm-encrypted to-device messages. Out of caution, several other checks have been audited or added. This attack requires coordination between a malicious home server and an attacker, so those who trust their home servers do not need a workaround. To avoid malicious backup attacks, one should not verify one's new logins using emoji/QR verifications methods until patched.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/matrix-org/matrix-ios-sdk
Files changed:
CHANGES.md
MatrixSDK.xcodeproj/project.pbxproj
MatrixSDK/Background/MXBackgroundSyncService.swift
MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift
MatrixSDK/Crypto/Algorithms/MXDecrypting.h
MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m
MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m
MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m
MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h
MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m
MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m
MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.h
MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.m
MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.h
MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.m
MatrixSDK/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManager.swift
MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m
MatrixSDK/Crypto/MXCrypto.m
MatrixSDK/Crypto/MXCrypto_Private.h
MatrixSDK/Crypto/MXOlmDevice.h
MatrixSDK/Crypto/MXOlmDevice.m
MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfo.swift
MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfoFactory.swift
MatrixSDK/Crypto/RoomKeys/MXRoomKeyResult.swift
MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m
MatrixSDK/MXSession.m
MatrixSDK/MatrixSDK.h
MatrixSDK/Utils/Categories/Dictionary.swift
MatrixSDK/Utils/MXDateProvider.swift
MatrixSDK/Utils/MXLRUCache.m
MatrixSDK/Utils/MXTools.swift
MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift
MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift
MatrixSDKTests/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContentUnitTests.swift
MatrixSDKTests/Crypto/KeySharing/Data/MXRoomKeyEventContentUnitTests.swift
MatrixSDKTests/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManagerUnitTests.swift
MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift
MatrixSDKTests/Crypto/RoomKeys/MXRoomKeyInfoFactoryUnitTests.swift
MatrixSDKTests/JSONModels/MXEventFixtures.swift
MatrixSDKTests/MXCryptoSecretShareTests.m
MatrixSDKTests/MXCryptoTests.m
MatrixSDKTests/MXToolsUnitTests.m
MatrixSDKTests/TestPlans/UnitTests.xctestplan
MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan
Commit message: Resolve multiple CVEs

CVE-2022-39255
CVE-2022-39257


Vulnerable code:
        guard let content = event.content else {
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: incomplete event content: \(String(describing: event.jsonDictionary()))")
        guard let roomId = content["room_id"] as? String,
            let sessionId = content["session_id"] as? String,
            let sessionKey = content["session_key"] as? String,
            var senderKey = event.senderKey else {
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: incomplete event: \(String(describing: event.jsonDictionary()))")
        var forwardingKeyChain: [String] = []
        var exportFormat: Bool = false
        var keysClaimed: [String: String] = [:]
        
        switch event.eventType {
        case .roomKey:
            keysClaimed = event.keysClaimed as! [String: String]
        case .roomForwardedKey:
            exportFormat = true
            
            if let array = content["forwarding_curve25519_key_chain"] as? [String] {
                forwardingKeyChain = array
            }
            forwardingKeyChain.append(senderKey)
            
            if let senderKeyInContent = content["sender_key"] as? String {
                senderKey = senderKeyInContent
            } else {
                return
            }
            
            guard let ed25519Key = event.content["sender_claimed_ed25519_key"] as? String else {
                return
            }
            
            keysClaimed = [
                "ed25519": ed25519Key
            ]
        default:
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: Not supported type: \(event.eventType)")
            return
        
        let sharedHistory = (content[kMXSharedHistoryKeyName] as? Bool) ?? false
        olmDevice.addInboundGroupSession(sessionId,
                                         sessionKey: sessionKey,
                                         roomId: roomId,
                                         senderKey: senderKey,
                                         forwardingCurve25519KeyChain: forwardingKeyChain,
                                         keysClaimed: keysClaimed,
                                         exportFormat: exportFormat,
                                         sharedHistory: sharedHistory)
        let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callSelectAnswer, .callHangup, .callReject, .callNegotiate, .callReplaces, .callRejectReplacement, .callAssertedIdentity, .callAssertedIdentityUnstable, .reaction, .receipt, .roomTombStone, .keyVerificationStart, .keyVerificationAccept, .keyVerificationKey, .keyVerificationMac, .keyVerificationCancel, .keyVerificationDone, .taggedEvents, .spaceChild, .spaceOrder, .pollStart, .pollResponse, .pollEnd, .beaconInfo, .beacon]
@class MXCrypto, MXOlmInboundGroupSession;
 * Handle a key event.
 *
 * @param event the key event.
    NSDictionary *content = event.content;
    NSString *roomId, *sessionId, *sessionKey;

    MXJSONModelSetString(roomId, content[@"room_id"]);
    MXJSONModelSetString(sessionId, content[@"session_id"]);
    MXJSONModelSetString(sessionKey, content[@"session_key"]);

    if (!roomId || !sessionId || !sessionKey)
        MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: Key event is missing fields");

    NSString *senderKey = event.senderKey;
    if (!senderKey)
    {
        MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: Key event has no sender key (not encrypted?)");
        return;
    }

    NSArray<NSString*> *forwardingKeyChain;
    BOOL exportFormat = NO;
    NSDictionary *keysClaimed;
    BOOL sharedHistory = NO;
    if (content[kMXSharedHistoryKeyName] != nil)
    {
        MXJSONModelSetBoolean(sharedHistory, content[kMXSharedHistoryKeyName]);
    }

    if (event.eventType == MXEventTypeRoomForwardedKey)
    {
        exportFormat = YES;
        MXJSONModelSetArray(forwardingKeyChain, content[@"forwarding_curve25519_key_chain"]);
        if (!forwardingKeyChain)
        {
            forwardingKeyChain = @[];
        }

        // copy content before we modify it
        NSMutableArray *forwardingKeyChain2 = [NSMutableArray arrayWithArray:forwardingKeyChain];
        [forwardingKeyChain2 addObject:senderKey];
        forwardingKeyChain = forwardingKeyChain2;

        MXJSONModelSetString(senderKey, content[@"sender_key"]);
        if (!senderKey)
        {
            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: forwarded_room_key event is missing sender_key field");
            return;
        }

        NSString *ed25519Key;
        MXJSONModelSetString(ed25519Key, content[@"sender_claimed_ed25519_key"]);
        if (!ed25519Key)
        {
            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: forwarded_room_key_event is missing sender_claimed_ed25519_key field");
            return;
        }

        keysClaimed = @{
                        @"ed25519": ed25519Key
                        };
    }
    else
    {
        keysClaimed = event.keysClaimed;
    MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: Adding key for megolm session %@|%@ from %@ event", senderKey, sessionId, event.type);

    [olmDevice addInboundGroupSession:sessionId
                           sessionKey:sessionKey
                               roomId:roomId
                            senderKey:senderKey
         forwardingCurve25519KeyChain:forwardingKeyChain
                          keysClaimed:keysClaimed
                         exportFormat:exportFormat
                        sharedHistory:sharedHistory];
    [self retryDecryption:senderKey sessionId:content[@"session_id"] complete:^(BOOL allDecrypted) {
                                                 @"algorithm": content[@"algorithm"],
                                                 @"room_id": content[@"room_id"],
                                                 @"session_id": content[@"session_id"],
                                                 @"sender_key": senderKey
/**
 Flag indicating the backup trusted locally.
 */
@property (nonatomic, getter=isTrustedLocally) BOOL trustedLocally;

        _trustedLocally = NO;
    NSData *privateKey = self.privateKeyFromCryptoStore;
    if (privateKey)
    {
        id<MXKeyBackupAlgorithm> algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey];
        if ([algorithm keyMatches:privateKey error:nil])
        {
            MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersionFromCryptoQueue: Backup is trusted locally");
            keyBackupVersionTrust.trustedLocally = YES;
        }
    }

    keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally;
@interface MXCrypto ()
    id<MXEncrypting> alg = roomEncryptors[event.roomId];
    if (!alg)
    {
        // No encrypting in this room
        return;
    }

                 sharedHistory:(BOOL)sharedHistory;
                 sharedHistory:(BOOL)sharedHistory;
        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Update for megolm session %@|%@", senderKey, sessionId);
        // If our existing session is better, we keep it
        if (existingSession.session.firstKnownIndex <= session.session.firstKnownIndex)
            MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skip it. The index of the incoming session is higher (%@ vs %@)", @(session.session.firstKnownIndex), @(existingSession.session.firstKnownIndex));
            return NO;
                MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
    if (events.count == 0)
    [self decryptEvents:events inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
        for (MXEvent *event in events)
            sharedHistory: Bool
            let event = makeRoomKeyEvent(sharedHistory: eventValue)
    
    // MARK: - Helpers
    
    /// Create a room key event with some random but valid data that can be used to create a new inbound session.
    private func makeRoomKeyEvent(sharedHistory: Bool? = nil) -> MXEvent? {
        let event = MXEvent(fromJSON: [
            "sender_key": senderKey,
        ])
        
        var content: [String: Any] = [
            "room_id": roomId1,
            "session_id": sessionId1,
            "session_key": "123",
            "algorithm": "456",
        ]
        if let sharedHistory = sharedHistory {
            content["org.matrix.msc3061.shared_history"] = sharedHistory
        }
        
        let result = MXEventDecryptionResult()
        result.senderCurve25519Key = "XYZ"
        result.clearEvent = ["content": content]
        event?.setClearData(result)
        return event
    }
            sharedHistory: sharedHistory
 - Alice trusts the new device
            // - Alice trusts the new device
        "MXTrustLevelSourceUnitTests"
        "MXTrustLevelSourceUnitTests"

Fixed code:
## Changes in 0.23.19 (2022-09-28)

🐛 Bugfixes

- CVE-2022-39255: Olm/Megolm protocol confusion ([Security advisory](https://github.com/matrix-org/matrix-ios-sdk/security/advisories/GHSA-hw6g-j8v6-9hcm))
- CVE-2022-39257: Impersonation via forwarded Megolm sessions ([Security advisory](https://github.com/matrix-org/matrix-ios-sdk/security/advisories/GHSA-qxr3-5jmq-xcf4))

		ED01915228C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */; };
		ED01915328C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */; };
		ED01915428C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
		ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
		ED01915628C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */; };
		ED01915728C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */; };
		ED01915828C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
		ED01915928C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
		ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */; };
		ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */; };
		ED6DAC0228C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */; };
		ED6DAC0328C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */; };
		ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */; };
		ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */; };
		ED6DAC0A28C784AE00ECDCB6 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */; };
		ED6DAC0B28C784AE00ECDCB6 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */; };
		ED6DAC0D28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */; };
		ED6DAC0E28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */; };
		ED6DAC1128C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */; };
		ED6DAC1228C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */; };
		ED6DAC1528C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */; };
		ED6DAC1628C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */; };
		ED6DAC1828C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */; };
		ED6DAC1928C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */; };
		ED6DAC1B28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */; };
		ED6DAC1C28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */; };
		ED6DAC1E28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */; };
		ED6DAC1F28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */; };
		ED6DAC2128C7A51400ECDCB6 /* MXDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */; };
		ED6DAC2228C7A51400ECDCB6 /* MXDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */; };
		ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXForwardedRoomKeyEventContent.m; sourceTree = "<group>"; };
		ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomKeyEventContent.h; sourceTree = "<group>"; };
		ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomKeyEventContent.m; sourceTree = "<group>"; };
		ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXForwardedRoomKeyEventContent.h; sourceTree = "<group>"; };
		ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfoFactory.swift; sourceTree = "<group>"; };
		ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfo.swift; sourceTree = "<group>"; };
		ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXForwardedRoomKeyEventContentUnitTests.swift; sourceTree = "<group>"; };
		ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
		ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyEventContentUnitTests.swift; sourceTree = "<group>"; };
		ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfoFactoryUnitTests.swift; sourceTree = "<group>"; };
		ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXMemoryCryptoStore.swift; sourceTree = "<group>"; };
		ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyResult.swift; sourceTree = "<group>"; };
		ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXUnrequestedForwardedRoomKeyManager.swift; sourceTree = "<group>"; };
		ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXUnrequestedForwardedRoomKeyManagerUnitTests.swift; sourceTree = "<group>"; };
		ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXDateProvider.swift; sourceTree = "<group>"; };
				ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */,
				ED6DAC0428C771D500ECDCB6 /* RoomKeys */,
				ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */,
				ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */,
				ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */,
				ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */,
				ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */,
				ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */,
				ED6DAC1328C78D3700ECDCB6 /* Store */,
				ED6DAC0528C77E0600ECDCB6 /* Data */,
				ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */,
		ED6DAC0428C771D500ECDCB6 /* RoomKeys */ = {
			isa = PBXGroup;
			children = (
				ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */,
				ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */,
				ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */,
			);
			path = RoomKeys;
			sourceTree = "<group>";
		};
		ED6DAC0528C77E0600ECDCB6 /* Data */ = {
			isa = PBXGroup;
			children = (
				ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */,
				ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */,
			);
			path = Data;
			sourceTree = "<group>";
		};
		ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */ = {
			isa = PBXGroup;
			children = (
				ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */,
			);
			path = RoomKeys;
			sourceTree = "<group>";
		};
		ED6DAC1328C78D3700ECDCB6 /* Store */ = {
			isa = PBXGroup;
			children = (
				ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */,
			);
			path = Store;
			sourceTree = "<group>";
		};
				ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */,
				ED01915828C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
				ED01915428C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
				ED01915928C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
				ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
				ED6DAC0228C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
				ED01915228C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
				ED01915628C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
				ED6DAC1828C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
				ED6DAC1B28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
				ED6DAC0A28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
				ED6DAC2228C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
				ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
				ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC0D28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC1128C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
				ED6DAC1528C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
				ED6DAC1E28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
				ED6DAC0328C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
				ED01915328C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
				ED01915728C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
				ED6DAC1928C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
				ED6DAC1C28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
				ED6DAC0B28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
				ED6DAC2128C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
				ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
				ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC0E28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC1228C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
				ED6DAC1628C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
				ED6DAC1F28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
        //   only handle supported events
        guard MXTools.isSupportedToDeviceEvent(event) else {
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ignore unsupported event")
            return
        }
        
        guard let userId = credentials.userId else {
            MXLog.error("[MXBackgroundSyncService] handleToDeviceEvent: Cannot get userId")
        let factory = MXRoomKeyInfoFactory(myUserId: userId, store: cryptoStore)
        guard let key = factory.roomKey(for: event) else {
            MXLog.error("[MXBackgroundSyncService] handleToDeviceEvent: Cannot create megolm key from event")
        switch key.type {
        case .safe:
            olmDevice.addInboundGroupSession(
                key.info.sessionId,
                sessionKey: key.info.sessionKey,
                roomId: key.info.roomId,
                senderKey: key.info.senderKey,
                forwardingCurve25519KeyChain: key.info.forwardingKeyChain,
                keysClaimed: key.info.keysClaimed,
                exportFormat: key.info.exportFormat,
                sharedHistory: key.info.sharedHistory,
                untrusted: key.type != .safe
            )
        case .unsafe:
            MXLog.warning("[MXBackgroundSyncService] handleToDeviceEvent: Ignoring unsafe keys")
        case .unrequested:
            MXLog.warning("[MXBackgroundSyncService] handleToDeviceEvent: Ignoring unrequested keys")
    case secretRequest
    case secretSend
    case secretStorageDefaultKey
        case .secretRequest: return kMXEventTypeStringSecretRequest
        case .secretSend: return kMXEventTypeStringSecretSend
        case .secretStorageDefaultKey: return kMXEventTypeStringSecretStorageDefaultKey
        let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callSelectAnswer, .callHangup, .callReject, .callNegotiate, .callReplaces, .callRejectReplacement, .callAssertedIdentity, .callAssertedIdentityUnstable, .reaction, .receipt, .roomTombStone, .keyVerificationStart, .keyVerificationAccept, .keyVerificationKey, .keyVerificationMac, .keyVerificationCancel, .keyVerificationDone, .secretRequest, .secretSend, .secretStorageDefaultKey, .taggedEvents, .spaceChild, .spaceOrder, .pollStart, .pollResponse, .pollEnd, .beaconInfo, .beacon]
@class MXCrypto, MXOlmInboundGroupSession, MXRoomKeyResult;
 Handle a key event.
 
 @param event the key event.
/**
 Handle new room key
 
 @param key the domain object with key details and safety
 */
- (void)onRoomKey:(MXRoomKeyResult*)key;

#import "MXForwardedRoomKeyEventContent.h"

// Factory to create room key info
@property (nonatomic, strong) MXRoomKeyInfoFactory *roomKeyInfoFactory;

        _roomKeyInfoFactory = [[MXRoomKeyInfoFactory alloc] initWithMyUserId:crypto.mxSession.credentials.userId store:crypto.store];
    MXRoomKeyResult *key = [self.roomKeyInfoFactory roomKeyFor:event];
    if (!key)
        MXLogError(@"[MXMegolmDecryption] onRoomKeyEvent: Cannot create megolm key from event");
    
    switch (key.type) {
        case MXRoomKeyTypeSafe:
            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: Adding key for megolm session %@|%@ from %@ event", key.info.senderKey, key.info.sessionId, event.type);
            [self onRoomKey:key];
            break;
        case MXRoomKeyTypeUnsafe:
            MXLogWarning(@"[MXMegolmDecryption] onRoomKeyEvent: Ignoring unsafe key");
            break;
        case MXRoomKeyTypeUnrequested:
            [crypto handleUnrequestedRoomKeyInfo:key.info senderId:event.sender senderKey:event.senderKey];
            break;
        default:
            MXLogFailureDetails(@"[MXMegolmDecryption] onRoomKeyEvent: Unknown key type", @{
                @"key_type": @(key.type)
            });
            break;
}
- (void)onRoomKey:(MXRoomKeyResult *)key
{
    MXRoomKeyInfo *keyInfo = key.info;
    [olmDevice addInboundGroupSession:keyInfo.sessionId
                           sessionKey:keyInfo.sessionKey
                               roomId:keyInfo.roomId
                            senderKey:keyInfo.senderKey
         forwardingCurve25519KeyChain:keyInfo.forwardingKeyChain
                          keysClaimed:keyInfo.keysClaimed
                         exportFormat:keyInfo.exportFormat
                        sharedHistory:keyInfo.sharedHistory
                            untrusted:key.type != MXRoomKeyTypeSafe];
    [self retryDecryption:keyInfo.senderKey sessionId:keyInfo.sessionId complete:^(BOOL allDecrypted) {
                                                 @"algorithm": keyInfo.algorithm,
                                                 @"room_id": keyInfo.roomId,
                                                 @"session_id": keyInfo.sessionId,
                                                 @"sender_key": keyInfo.senderKey
                                   untrusted:NO
- (void)onRoomKeyInfo:(MXRoomKeyInfo *)keyInfo
{
    // No impact for olm
}

// 
// 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 <MatrixSDK/MatrixSDK.h>

NS_ASSUME_NONNULL_BEGIN

@interface MXForwardedRoomKeyEventContent : MXJSONModel

/**
 The encryption algorithm the key in this event is to be used with
 */
@property (nonatomic) NSString *algorithm;

/**
 The room where the key is used
 */
@property (nonatomic) NSString *roomId;

/**
 The Curve25519 key of the device which initiated the session originally
 */
@property (nonatomic) NSString *senderKey;

/**
 The ID of the session that the key is for
 */
@property (nonatomic) NSString *sessionId;

/**
 The key to be exchanged
 */
@property (nonatomic) NSString *sessionKey;

/**
 Chain of Curve25519 keys
 
 It starts out empty, but each time the key is forwarded to another device, the previous sender
 in the chain is added to the end of the list.
 
 For example, if the key is forwarded from A to B to C, this field is empty between A and B,
 and contains A's Curve25519 key between B and C
 */
@property (nonatomic) NSArray<NSString*> *forwardingCurve25519KeyChain;

/**
 The Ed25519 key of the device which initiated the session originally
 
 It is 'claimed' because the receiving device has no way to tell that the original room_key actually
 came from a device which owns the private part of this key unless they have done device verification.
 */
@property (nonatomic) NSString *senderClaimedEd25519Key;

/**
 MSC3061 Identifies keys that were sent when the room's visibility setting was set to `world_readable` or `shared`
 */
@property (nonatomic) BOOL sharedHistory;

@end

NS_ASSUME_NONNULL_END
// 
// 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 "MXForwardedRoomKeyEventContent.h"

static NSString* const kJSONKeyAlgorithm = @"algorithm";
static NSString* const kJSONKeyRoomId = @"room_id";
static NSString* const kJSONKeySenderKey = @"sender_key";
static NSString* const kJSONKeySessionId = @"session_id";
static NSString* const kJSONKeySessionKey = @"session_key";
static NSString* const kJSONKeyForwardingCurve25519KeyChain = @"forwarding_curve25519_key_chain";
static NSString* const kJSONKeySenderClaimedEd25519Key = @"sender_claimed_ed25519_key";

@implementation MXForwardedRoomKeyEventContent

#pragma mark - MXJSONModel

+ (id)modelFromJSON:(NSDictionary *)JSONDictionary
{
    MXForwardedRoomKeyEventContent *result = [[MXForwardedRoomKeyEventContent alloc] init];
    MXJSONModelSetString(result.algorithm, JSONDictionary[kJSONKeyAlgorithm]);
    MXJSONModelSetString(result.roomId, JSONDictionary[kJSONKeyRoomId]);
    MXJSONModelSetString(result.sessionId, JSONDictionary[kJSONKeySessionId]);
    MXJSONModelSetString(result.sessionKey, JSONDictionary[kJSONKeySessionKey]);
    MXJSONModelSetString(result.senderKey, JSONDictionary[kJSONKeySenderKey]);
    MXJSONModelSetString(result.senderClaimedEd25519Key, JSONDictionary[kJSONKeySenderClaimedEd25519Key]);
    if (!result.algorithm || !result.roomId || !result.sessionId || !result.sessionKey || !result.senderKey || !result.senderClaimedEd25519Key)
    {
        MXLogError(@"[MXRoomKeyEventContent] modelFromJSON: Key event is missing fields");
        return nil;
    }
    
    MXJSONModelSetArray(result.forwardingCurve25519KeyChain, JSONDictionary[kJSONKeyForwardingCurve25519KeyChain] ?: @[]);
    MXJSONModelSetBoolean(result.sharedHistory, JSONDictionary[kMXSharedHistoryKeyName]);

    return result;
}

- (NSDictionary *)JSONDictionary
{
    NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary];
    JSONDictionary[kJSONKeyAlgorithm] = _algorithm;
    JSONDictionary[kJSONKeyRoomId] = _roomId;
    JSONDictionary[kJSONKeySenderKey] = _senderKey;
    JSONDictionary[kJSONKeySessionId] = _sessionId;
    JSONDictionary[kJSONKeySessionKey] = _sessionKey;
    JSONDictionary[kJSONKeyForwardingCurve25519KeyChain] = _forwardingCurve25519KeyChain;
    JSONDictionary[kJSONKeySenderClaimedEd25519Key] = _senderClaimedEd25519Key;
    JSONDictionary[kMXSharedHistoryKeyName] = @(_sharedHistory);
    return JSONDictionary;
}

@end
// 
// 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 <MatrixSDK/MatrixSDK.h>

NS_ASSUME_NONNULL_BEGIN

@interface MXRoomKeyEventContent : MXJSONModel

/**
 The encryption algorithm the key in this event is to be used with
 */
@property (nonatomic) NSString *algorithm;

/**
 The room where the key is used
 */
@property (nonatomic) NSString *roomId;

/**
 The ID of the session that the key is for
 */
@property (nonatomic) NSString *sessionId;

/**
 The key to be exchanged
 */
@property (nonatomic) NSString *sessionKey;

/**
 MSC3061 Identifies keys that were sent when the room's visibility setting was set to `world_readable` or `shared`
 */
@property (nonatomic) BOOL sharedHistory;

@end

NS_ASSUME_NONNULL_END
// 
// 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 "MXRoomKeyEventContent.h"

static NSString* const kJSONKeyAlgorithm = @"algorithm";
static NSString* const kJSONKeyRoomId = @"room_id";
static NSString* const kJSONKeySessionId = @"session_id";
static NSString* const kJSONKeySessionKey = @"session_key";

@implementation MXRoomKeyEventContent

#pragma mark - MXJSONModel

+ (id)modelFromJSON:(NSDictionary *)JSONDictionary
{
    MXRoomKeyEventContent *result = [MXRoomKeyEventContent new];
    MXJSONModelSetString(result.algorithm, JSONDictionary[kJSONKeyAlgorithm]);
    MXJSONModelSetString(result.roomId, JSONDictionary[kJSONKeyRoomId]);
    MXJSONModelSetString(result.sessionId, JSONDictionary[kJSONKeySessionId]);
    MXJSONModelSetString(result.sessionKey, JSONDictionary[kJSONKeySessionKey]);
    if (!result.algorithm || !result.roomId || !result.sessionId || !result.sessionKey)
    {
        MXLogError(@"[MXRoomKeyEventContent] modelFromJSON: Key event is missing fields");
        return nil;
    }

    MXJSONModelSetBoolean(result.sharedHistory, JSONDictionary[kMXSharedHistoryKeyName]);
    

    return result;
}

- (NSDictionary *)JSONDictionary
{
    NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary];
    JSONDictionary[kJSONKeyAlgorithm] = _algorithm;
    JSONDictionary[kJSONKeyRoomId] = _roomId;
    JSONDictionary[kJSONKeySessionId] = _sessionId;
    JSONDictionary[kJSONKeySessionKey] = _sessionKey;
    JSONDictionary[kMXSharedHistoryKeyName] = @(_sharedHistory);
    return JSONDictionary;
}

@end
// 
// 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

@objc public protocol MXUnrequestedForwardedRoomKeyManagerDelegate: AnyObject {
    func downloadDeviceKeys(userId: String, completion: @escaping (MXUsersDevicesMap<MXDeviceInfo>) -> Void)
    func acceptRoomKey(keyInfo: MXRoomKeyInfo)
}

@objcMembers
public class MXUnrequestedForwardedRoomKeyManager: NSObject {
    private typealias RoomId = String
    private typealias UserId = String
    
    static let MaximumTimeInterval: TimeInterval = 10 * 60
    
    struct PendingKey {
        let info: MXRoomKeyInfo
        let date: Date
    }
    
    struct RoomInvite {
        let roomId: String
        let senderId: String
        let date: Date
    }
    
    public weak var delegate: MXUnrequestedForwardedRoomKeyManagerDelegate?
    
    private let dateProvider: MXDateProviding
    private var pendingKeys = [RoomId: [UserId: [PendingKey]]]()
    private var roomInvites = [RoomInvite]()
    
    override public init() {
        self.dateProvider = MXDateProvider()
    }
    
    init(dateProvider: MXDateProviding) {
        self.dateProvider = dateProvider
    }
    
    public func close() {
        pendingKeys = [:]
        roomInvites = []
    }
    
    public func addPendingKey(keyInfo: MXRoomKeyInfo, senderId: String, senderKey: String) {
        guard let delegate = delegate else {
            MXLog.error("[MXUnrequestedForwardedRoomKeyManager] addPendingKey: Delegate is not set")
            return
        }
        
        // If just invited by the user we may not yet have their keys locally
        delegate.downloadDeviceKeys(userId: senderId) { [weak self] keys in
            guard let self = self else { return }
            
            guard let userId = self.matchingUserId(in: keys, userId: senderId, identityKey: senderKey) else {
                MXLog.error("[MXUnrequestedForwardedRoomKeyManager] addPendingKey: senderId does not match the claimed senderKey")
                return
            }
            self.addPendingKey(keyInfo: keyInfo, confirmedSenderId: userId)
            self.processUnrequestedKeys()
        }
    }
    
    public func onRoomInvite(roomId: String, senderId: String) {
        roomInvites.append(
            .init(
                roomId: roomId,
                senderId: senderId,
                date: dateProvider.currentDate()
            )
        )
    }
    
    public func processUnrequestedKeys() {
        guard let delegate = delegate else {
            MXLog.error("[MXUnrequestedForwardedRoomKeyManager] processUnrequestedKeys: Delegate is not set")
            return
        }
        
        let now = dateProvider.currentDate()
        
        roomInvites.removeAll {
            !$0.date.isWithin(timeInterval: Self.MaximumTimeInterval, of: now)
        }
        
        for invite in roomInvites {
            guard let roomKeys = pendingKeys[invite.roomId] else {
                continue
            }
            
            for (senderId, keys) in roomKeys {
                if invite.senderId == senderId {
                    for key in keys {
                        guard key.date.isWithin(timeInterval: Self.MaximumTimeInterval, of: invite.date) else {
                            continue
                        }
                        delegate.acceptRoomKey(keyInfo: key.info)
                    }
                }
            }
            
            pendingKeys[invite.roomId] = nil
        }
    }
    
    // MARK: - Private
    
    private func matchingUserId(in deviceKeys: MXUsersDevicesMap<MXDeviceInfo>, userId: String, identityKey: String) -> String? {
        return deviceKeys
            .objects(forUser: userId)?
            .first { $0.identityKey == identityKey }
            .flatMap { $0.userId }
    }
    
    private func addPendingKey(keyInfo: MXRoomKeyInfo, confirmedSenderId: String) {
        if pendingKeys[keyInfo.roomId] == nil {
            pendingKeys[keyInfo.roomId] = [:]
        }
        
        if pendingKeys[keyInfo.roomId]![confirmedSenderId] == nil {
            pendingKeys[keyInfo.roomId]![confirmedSenderId] = []
        }
        
        pendingKeys[keyInfo.roomId]![confirmedSenderId]!.append(
            .init(
                info: keyInfo,
                date: dateProvider.currentDate()
            )
        )
    }
}

private extension Date {
    func isWithin(timeInterval: TimeInterval, of date: Date) -> Bool {
        return abs(self.timeIntervalSince(date)) < timeInterval
    }
}
    if (![self canAcceptSecretSendEvent:event])
    {
        MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Rejecting unacceptable secret");
        return;
    }
    
- (BOOL)canAcceptSecretSendEvent:(MXEvent*)event
{
    // No need to download keys, after a verification we already forced download
    MXDeviceInfo *sendingDevice = [self.crypto.store deviceWithIdentityKey:event.senderKey];
    if (!sendingDevice)
    {
        MXLogError(@"[MXSecretShareManager] canAcceptSecretSendEvent: Unknown sending device");
        return NO;
    }
    
    if (![sendingDevice.userId isEqualToString:self.crypto.mxSession.myUserId])
    {
        MXLogDebug(@"[MXSecretShareManager] canAcceptSecretSendEvent: Ignoring secret from another user");
        return NO;
    }
    
    if (!sendingDevice.trustLevel.isVerified)
    {
        MXLogDebug(@"[MXSecretShareManager] canAcceptSecretSendEvent: Ignoring secret from untrusted device");
        return NO;
    }
    return YES;
}


@interface MXCrypto () <MXUnrequestedForwardedRoomKeyManagerDelegate>
    // The manager for unrequested m.forwarded_room_keys
    MXUnrequestedForwardedRoomKeyManager *unrequestedForwardedRoomKeyManager;
    
        
        [self->unrequestedForwardedRoomKeyManager close];
        self->outgoingRoomKeyRequestManager = nil;
            [self->unrequestedForwardedRoomKeyManager processUnrequestedKeys];
        
        unrequestedForwardedRoomKeyManager = [[MXUnrequestedForwardedRoomKeyManager alloc] init];
        unrequestedForwardedRoomKeyManager.delegate = self;
- (void)handleUnrequestedRoomKeyInfo:(MXRoomKeyInfo *)keyInfo senderId:(NSString *)senderId senderKey:(NSString *)senderKey
{
    [unrequestedForwardedRoomKeyManager addPendingKeyWithKeyInfo:keyInfo senderId:senderId senderKey:senderKey];
}

    
    MXRoomMemberEventContent *content = [MXRoomMemberEventContent modelFromJSON:event.content];
    if ([userId isEqualToString:self.mxSession.credentials.userId] && [content.membership isEqualToString:kMXMembershipStringInvite])
    {
        [unrequestedForwardedRoomKeyManager onRoomInviteWithRoomId:event.roomId senderId:event.sender];
    }
    
#pragma mark - MXUnrequestedForwardedRoomKeyManagerDelegate

- (void)downloadDeviceKeysWithUserId:(NSString *)userId completion:(void (^)(MXUsersDevicesMap<MXDeviceInfo *> *))completion
{
    [self downloadKeys:@[userId] forceDownload:YES success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap, NSDictionary<NSString *,MXCrossSigningInfo *> *crossSigningKeysMap) {
        completion(usersDevicesInfoMap);
    } failure:^(NSError *error) {
        MXLogError(@"[MXCrypto]: Failed downloading keys for key forward manager");
        completion([[MXUsersDevicesMap alloc] init]);
    }];
}

- (void)acceptRoomKeyWithKeyInfo:(MXRoomKeyInfo *)keyInfo
{
    id<MXDecrypting> decryptor = [self getRoomDecryptor:keyInfo.roomId algorithm:keyInfo.algorithm];
    MXRoomKeyResult *key = [[MXRoomKeyResult alloc] initWithType:MXRoomKeyTypeUnsafe info:keyInfo];
    [decryptor onRoomKey:key];
}

@class MXRoomKeyInfo;

/**
 Handle forwarded room key that was not requested by this device
 
 @param keyInfo details about the key
 @param senderId userId of the person who sent us the key
 @param senderKey identity of the person who sent us the room key
 */
- (void)handleUnrequestedRoomKeyInfo:(MXRoomKeyInfo *)keyInfo senderId:(NSString *)senderId senderKey:(NSString *)senderKey;

                 sharedHistory:(BOOL)sharedHistory
                     untrusted:(BOOL)untrusted;
                 sharedHistory:(BOOL)sharedHistory
                     untrusted:(BOOL)untrusted
        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Considering updates for megolm session %@|%@", senderKey, sessionId);
        BOOL isExistingSessionBetter = existingSession.session.firstKnownIndex <= session.session.firstKnownIndex;
        if (isExistingSessionBetter)
            BOOL isNewSessionSafer = existingSession.isUntrusted && !session.isUntrusted;
            if (!isNewSessionSafer)
            {
                MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skip it. The index of the incoming session is higher (%@ vs %@)", @(session.session.firstKnownIndex), @(existingSession.session.firstKnownIndex));
                return NO;
            }
            
            if ([self connectsSession1:existingSession session2:session])
            {
                MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skipping new session, and upgrading the safety of existing session");
                [self upgradeSafetyForSession:existingSession];
                return NO;
            }
            else
            {
                MXLogWarning(@"[MXOlmDevice] addInboundGroupSession: Recieved a safer but disconnected key, which will override the existing unsafe key");
                existingSession = nil;
            }
    session.untrusted = untrusted;
- (void)upgradeSafetyForSession:(MXOlmInboundGroupSession *)session
{
    [self.store performSessionOperationWithGroupSessionWithId:session.session.sessionIdentifier senderKey:session.senderKey block:^(MXOlmInboundGroupSession *inboundGroupSession) {
        inboundGroupSession.untrusted = NO;
    }];
    if (MXSDKOptions.sharedInstance.enableGroupSessionCache)
    {
        @synchronized (self.inboundGroupSessionCache)
        {
            session.untrusted = NO;
            [self.inboundGroupSessionCache put:session.session.sessionIdentifier object:session];
        }
    }
}

- (BOOL)connectsSession1:(MXOlmInboundGroupSession *)session1 session2:(MXOlmInboundGroupSession *)session2
{
    // `connects` function will be moved to libolm in the future to avoid having to export the session
    NSUInteger lowestCommonIndex = MAX(session1.session.firstKnownIndex, session2.session.firstKnownIndex);
    MXMegolmSessionData *export1 = [session1 exportSessionDataAtMessageIndex:lowestCommonIndex];
    MXMegolmSessionData *export2 = [session2 exportSessionDataAtMessageIndex:lowestCommonIndex];
    return [export1.sessionKey isEqualToString:export2.sessionKey];
}

// 
// 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

/// Domain object representing a room key and its parameters
@objcMembers
public class MXRoomKeyInfo: NSObject {
    public let algorithm: String
    public let sessionId: String
    public let sessionKey: String
    public let roomId: String
    public let senderKey: String
    public let forwardingKeyChain: [String]?
    public let keysClaimed: [String: String]
    public let exportFormat: Bool
    public let sharedHistory: Bool
    
    public init(
        algorithm: String,
        sessionId: String,
        sessionKey: String,
        roomId: String,
        senderKey: String,
        forwardingKeyChain: [String]?,
        keysClaimed: [String: String],
        exportFormat: Bool,
        sharedHistory: Bool
    ) {
        self.algorithm = algorithm
        self.sessionId = sessionId
        self.sessionKey = sessionKey
        self.roomId = roomId
        self.senderKey = senderKey
        self.forwardingKeyChain = forwardingKeyChain
        self.keysClaimed = keysClaimed
        self.exportFormat = exportFormat
        self.sharedHistory = sharedHistory
        super.init()
    }
}

extension MXRoomKeyInfo {
    convenience init?(roomKey: MXRoomKeyEventContent, event: MXEvent) {
        guard let senderKey = event.senderKey, let keysClaimed = event.keysClaimed as? [String: String] else {
            return nil
        }
        
        self.init(
            algorithm: roomKey.algorithm,
            sessionId: roomKey.sessionId,
            sessionKey: roomKey.sessionKey,
            roomId: roomKey.roomId,
            senderKey: senderKey,
            forwardingKeyChain: nil,
            keysClaimed: keysClaimed,
            exportFormat: false,
            sharedHistory: roomKey.sharedHistory
        )
    }
}

extension MXRoomKeyInfo {
    convenience init(forwardedRoomKey: MXForwardedRoomKeyEventContent) {
        self.init(
            algorithm: forwardedRoomKey.algorithm,
            sessionId: forwardedRoomKey.sessionId,
            sessionKey: forwardedRoomKey.sessionKey,
            roomId: forwardedRoomKey.roomId,
            senderKey: forwardedRoomKey.senderKey,
            forwardingKeyChain: forwardedRoomKey.forwardingCurve25519KeyChain,
            keysClaimed: ["ed25519": forwardedRoomKey.senderClaimedEd25519Key],
            exportFormat: true,
            sharedHistory: forwardedRoomKey.sharedHistory
        )
    }
}
// 
// 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

@objcMembers
public class MXRoomKeyInfoFactory: NSObject {
    private let myUserId: String
    private let store: MXCryptoStore
    private let log = MXNamedLog(name: "MXRoomKeyFactory")
    
    public init(myUserId: String, store: MXCryptoStore) {
        self.myUserId = myUserId
        self.store = store
    }
    
    public func roomKey(for event: MXEvent) -> MXRoomKeyResult? {
        if event.eventType == .roomKey {
            return roomKeyEventInfo(for: event)
        } else if event.eventType == .roomForwardedKey {
            return forwardedRoomKeyEventInfo(for: event)
        } else {
            log.error("Unknown event type", context: event.eventType)
            return nil
        }
    }
    
    private func roomKeyEventInfo(for event: MXEvent) -> MXRoomKeyResult? {
        guard
            let content = MXRoomKeyEventContent(fromJSON: event.content),
            let info = MXRoomKeyInfo(roomKey: content, event: event)
        else {
            log.error("Invalid room key")
            return nil
        }
        
        return .init(type: .safe, info: info)
    }
    
    private func forwardedRoomKeyEventInfo(for event: MXEvent) -> MXRoomKeyResult? {
        guard let eventSenderKey = event.senderKey else {
            log.error("Unknown event sender")
            return nil
        }
        
        guard let content = MXForwardedRoomKeyEventContent(fromJSON: event.content) else {
            log.error("Invalid forwarded key")
            return nil
        }
        
        content.forwardingCurve25519KeyChain += [eventSenderKey]

        return .init(
            type: keyType(for: content, senderKey: eventSenderKey),
            info: .init(forwardedRoomKey: content)
        )
    }
    
    private func keyType(for content: MXForwardedRoomKeyEventContent, senderKey: String) -> MXRoomKeyType {
        if !hasPendingRequest(for: content) {
            log.debug("Key was not requested")
            return .unrequested
        } else if isMyVerifiedDevice(identityKey: senderKey) {
            return .safe
        } else {
            log.debug("Key forward is not from my verified device")
            return .unsafe
        }
    }
    
    private func isMyVerifiedDevice(identityKey: String) -> Bool {
        guard let device = store.device(withIdentityKey: identityKey) else {
            return false
        }
        return device.userId == myUserId && device.trustLevel.isVerified
    }
    
    private func hasPendingRequest(for content: MXForwardedRoomKeyEventContent) -> Bool {
        let request = store.outgoingRoomKeyRequest(withRequestBody: [
            "room_id": content.roomId,
            "algorithm": content.algorithm,
            "sender_key": content.senderKey,
            "session_id": content.sessionId
        ])
        return request != nil
    }
}
// 
// 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

@objc public enum MXRoomKeyType: Int {
    case safe
    case unsafe
    case unrequested
}

@objcMembers
public class MXRoomKeyResult: NSObject {
    public let type: MXRoomKeyType
    public let info: MXRoomKeyInfo
    
    public init(type: MXRoomKeyType, info: MXRoomKeyInfo) {
        self.type = type
        self.info = info
    }
}
        
        MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
                if ([device.deviceId isEqualToString:otherUserMasterKeys.keys])
                {
                    MXLogWarning(@"[MXKeyVerification][MXSASTransaction] verifyMacs: Device id should not be the same as master key");
                    cancelCode = MXTransactionCancelCode.invalidMessage;
                    break;
                }
                
    NSMutableArray *supportedEvents = [NSMutableArray arrayWithCapacity:events.count];
    for (MXEvent *event in events)
    {
        if ([MXTools isSupportedToDeviceEvent:event])
        {
            [supportedEvents addObject:event];
        }
    }

    if (supportedEvents.count == 0)
    [self decryptEvents:supportedEvents inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
        for (MXEvent *event in supportedEvents)
#import "MXRoomKeyEventContent.h"
#import "MXForwardedRoomKeyEventContent.h"
// 
// 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

internal extension Dictionary {
    func adding(key: Key, value: Value) -> [Key: Value] {
        var dict = self
        dict[key] = value
        return dict
    }
    
    func removing(key: Key) -> [Key: Value] {
        var dict = self
        dict[key] = nil
        return dict
    }
}
// 
// 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

protocol MXDateProviding {
    func currentDate() -> Date
}

struct MXDateProvider: MXDateProviding {
    func currentDate() -> Date {
        Date()
    }
}
            else
            {
                MXLRUCacheItem* item = [cachedObjects objectAtIndex:pos];
                item.object = object;
            }

    @objc
    /// Checks whether a given to-device event is supported or not.
    /// - Parameter event: Event to be checked
    /// - Returns: `true` if the event is supported, otherwise `false`
    static func isSupportedToDeviceEvent(_ event: MXEvent) -> Bool {
        if event.isEncrypted {
            // only support OLM encrypted events
            let algorithm = event.wireContent["algorithm"] as? String
            guard algorithm == kMXCryptoOlmAlgorithm else {
                MXLog.debug("[MXTools] isSupportedToDeviceEvent: not supported event encrypted with other than OLM algorithm: \(String(describing: algorithm))")
                return false
            }
        } else {
            // define unsupported plain event types
            let unsupportedPlainEvents = Set([
                MXEventType.roomKey.identifier,
                MXEventType.roomForwardedKey.identifier,
                MXEventType.secretSend.identifier
            ])
            // make sure that the event type is supported
            if unsupportedPlainEvents.contains(event.type) {
                MXLog.debug("[MXTools] isSupportedToDeviceEvent: not supported plain event with type: \(String(describing: event.type))")
                return false
            }
        }

        return true
    }
            sharedHistory: Bool,
            untrusted: Bool
            let event = MXEvent.roomKeyFixture(
                sharedHistory: eventValue
            )
//
// 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 OLMKit

public class MXMemoryCryptoStore: NSObject, MXCryptoStore {

    private static var stores: [MXCredentials: MXMemoryCryptoStore] = [:]

    private let credentials: MXCredentials
    private var storeAccount: Account?
    private var devices: [String: [MXDeviceInfo]] = [:]
    private var algorithms: [String: RoomAlgorithm] = [:]
    private var inboundSessions: [InboundSession] = []
    private var outboundSessions: [String: MXOlmOutboundGroupSession] = [:]
    private var secrets: [String: String] = [:]
    private var incomingRoomKeyRequestsMap: [String: MXIncomingRoomKeyRequest] = [:]
    private var outgoingRoomKeyRequests: [String: MXOutgoingRoomKeyRequest] = [:]
    private var olmSessions: [OlmSessionMapKey: MXOlmSession] = [:]
    private var crossSigningKeysMap: [String: MXCrossSigningInfo] = [:]
    private var sharedOutboundSessions: [SharedOutboundSession] = []

    // MARK: - MXCryptoStore

    public required init!(credentials: MXCredentials!) {
        self.credentials = credentials
        storeAccount = Account()
        storeAccount?.userId = credentials.userId
        storeAccount?.deviceId = credentials.deviceId
        storeAccount?.cryptoVersion = MXCryptoVersion(rawValue: MXCryptoVersion.versionCount.rawValue - 1) ?? .versionUndefined
        super.init()
    }

    public static func hasData(for credentials: MXCredentials!) -> Bool {
        stores[credentials] != nil
    }

    public static func createStore(with credentials: MXCredentials!) -> Self! {
        if let existingStore = stores[credentials] as? Self {
            return existingStore
        }
        if let newStore = Self(credentials: credentials) {
            stores[credentials] = newStore
            return newStore
        }
        return nil
    }

    public static func delete(with credentials: MXCredentials!) {
        stores.removeValue(forKey: credentials)
    }

    public static func deleteAllStores() {
        stores.removeAll()
    }

    public static func deleteReadonlyStore(with credentials: MXCredentials!) {
        // no-op
    }

    public func open(_ onComplete: (() -> Void)!, failure: ((Error?) -> Void)!) {
        onComplete?()
    }

    // MARK: - Device ID

    public func storeDeviceId(_ deviceId: String!) {
        storeAccount?.deviceId = deviceId
    }

    public func deviceId() -> String! {
        storeAccount?.deviceId
    }

    // MARK: - Account

    public func setAccount(_ account: OLMAccount!) {
        storeAccount?.olmAccount = account
    }

    public func account() -> OLMAccount! {
        storeAccount?.olmAccount
    }

    public func performAccountOperation(_ block: ((OLMAccount?) -> Void)!) {
        block?(storeAccount?.olmAccount)
    }

    // MARK: - Device Sync Token

    public func storeDeviceSyncToken(_ deviceSyncToken: String!) {
        storeAccount?.deviceSyncToken = deviceSyncToken
    }

    public func deviceSyncToken() -> String! {
        storeAccount?.deviceSyncToken
    }

    // MARK: - Devices

    public func storeDevice(forUser userId: String!, device: MXDeviceInfo!) {
        if devices[userId] == nil {
            devices[userId] = []
        }
        devices[userId]?.append(device)
    }

    public func device(withDeviceId deviceId: String!, forUser userId: String!) -> MXDeviceInfo! {
        devices[userId]?.first { $0.deviceId == deviceId }
    }

    public func device(withIdentityKey identityKey: String!) -> MXDeviceInfo! {
        Array(devices.values).flatMap { $0 }.first { $0.identityKey == identityKey }
    }

    public func storeDevices(forUser userId: String!, devices: [String : MXDeviceInfo]!) {
        if self.devices[userId] != nil {
            // Reset all previously stored devices for this user
            self.devices.removeValue(forKey: userId)
        }

        self.devices[userId] = Array(devices.values)
    }

    public func devices(forUser userId: String!) -> [String : MXDeviceInfo]! {
        let devices = devices[userId] ?? []

        var result: [String: MXDeviceInfo] = [:]

        for device in devices {
            result[device.deviceId] = device
        }

        return result
    }

    // MARK: - Device Tracking Status

    public func deviceTrackingStatus() -> [String : NSNumber]! {
        storeAccount?.deviceTrackingStatus
    }

    public func storeDeviceTrackingStatus(_ statusMap: [String : NSNumber]!) {
        storeAccount?.deviceTrackingStatus = statusMap
    }

    // MARK: - Cross Signing Keys

    public func storeCrossSigningKeys(_ crossSigningInfo: MXCrossSigningInfo!) {
        crossSigningKeysMap[crossSigningInfo.userId] = crossSigningInfo
    }

    public func crossSigningKeys(forUser userId: String!) -> MXCrossSigningInfo! {
        crossSigningKeysMap[userId]
    }

    public func crossSigningKeys() -> [MXCrossSigningInfo]! {
        Array(crossSigningKeysMap.values)
    }

    // MARK: - Room Algorithm

    public func storeAlgorithm(forRoom roomId: String!, algorithm: String!) {
        algorithms[roomId] = RoomAlgorithm(algorithm: algorithm)
    }

    public func algorithm(forRoom roomId: String!) -> String! {
        algorithms[roomId]?.algorithm
    }

    // MARK: - OLM Session

    public func store(_ session: MXOlmSession!, forDevice deviceKey: String!) {
        let key = OlmSessionMapKey(sessionId: session.session.sessionIdentifier(), deviceKey: deviceKey)
        olmSessions[key] = session
    }

    public func session(withDevice deviceKey: String!, andSessionId sessionId: String!) -> MXOlmSession! {
        let key = OlmSessionMapKey(sessionId: sessionId, deviceKey: deviceKey)
        return olmSessions[key]
    }

    public func performSessionOperation(withDevice deviceKey: String!, andSessionId sessionId: String!, block: ((MXOlmSession?) -> Void)!) {
        let session = session(withDevice: deviceKey, andSessionId: sessionId)
        block?(session)
    }

    public func sessions(withDevice deviceKey: String!) -> [MXOlmSession]! {
        Array(olmSessions.filter { $0.key.deviceKey == deviceKey }.values)
    }

    // MARK: - Inbound Group Sessions

    public func store(_ sessions: [MXOlmInboundGroupSession]!) {
        inboundSessions.append(contentsOf: sessions.map { InboundSession(session: $0) } )
    }

    public func inboundGroupSession(withId sessionId: String!, andSenderKey senderKey: String!) -> MXOlmInboundGroupSession! {
        inboundSessions.first { $0.sessionId == sessionId && $0.session.senderKey == senderKey }?.session
    }

    public func performSessionOperationWithGroupSession(withId sessionId: String!, senderKey: String!, block: ((MXOlmInboundGroupSession?) -> Void)!) {
        let session = inboundGroupSession(withId: sessionId, andSenderKey: senderKey)
        block?(session)
    }

    public func inboundGroupSessions() -> [MXOlmInboundGroupSession]! {
        inboundSessions.map { $0.session }
    }

    public func inboundGroupSessions(withSessionId sessionId: String!) -> [MXOlmInboundGroupSession]! {
        inboundSessions.filter { $0.sessionId == sessionId }.map { $0.session }
    }

    public func removeInboundGroupSession(withId sessionId: String!, andSenderKey senderKey: String!) {
        inboundSessions.removeAll { $0.sessionId == sessionId && $0.session.senderKey == senderKey }
    }

    // MARK: - Outbound Group Sessions

    public func store(_ session: OLMOutboundGroupSession!, withRoomId roomId: String!) -> MXOlmOutboundGroupSession! {
        let creationTime: TimeInterval

        if let existingSession = outboundSessions[roomId],
           existingSession.sessionId == session.sessionIdentifier() {
            // Update the existing one
            creationTime = existingSession.creationTime
        } else {
            creationTime = Date().timeIntervalSince1970
        }

        if let newSession = MXOlmOutboundGroupSession(session: session, roomId: roomId, creationTime: creationTime) {
            outboundSessions[roomId] = newSession
            return newSession
        }

        return nil
    }

    public func outboundGroupSession(withRoomId roomId: String!) -> MXOlmOutboundGroupSession! {
        outboundSessions[roomId]
    }

    public func outboundGroupSessions() -> [MXOlmOutboundGroupSession]! {
        Array(outboundSessions.values)
    }

    public func removeOutboundGroupSession(withRoomId roomId: String!) {
        outboundSessions.removeValue(forKey: roomId)
    }

    // MARK: - Shared Devices

    public func storeSharedDevices(_ devices: MXUsersDevicesMap<NSNumber>!, messageIndex: UInt, forOutboundGroupSessionInRoomWithId roomId: String!, sessionId: String!) {
        for userId in devices.userIds() {
            for deviceId in devices.deviceIds(forUser: userId) {
                guard let device = device(withDeviceId: deviceId, forUser: userId) else {
                    continue
                }

                let session = SharedOutboundSession(roomId: roomId, sessionId: sessionId, device: device, messageIndex: messageIndex)
                sharedOutboundSessions.append(session)
            }
        }
    }

    public func sharedDevicesForOutboundGroupSessionInRoom(withId roomId: String!, sessionId: String!) -> MXUsersDevicesMap<NSNumber>! {
        let result = MXUsersDevicesMap<NSNumber>()

        let sessions = sharedOutboundSessions.filter { $0.roomId == roomId && $0.sessionId == sessionId }

        for session in sessions {
            result.setObject(NSNumber(value: session.messageIndex),
                             forUser: session.device.userId,
                             andDevice: session.device.deviceId)
        }

        return result
    }

    public func messageIndexForSharedDeviceInRoom(withId roomId: String!, sessionId: String!, userId: String!, deviceId: String!) -> NSNumber! {
        guard let index = sharedOutboundSessions.first(where: { $0.roomId == roomId
            && $0.sessionId == sessionId
            && $0.device.deviceId == deviceId })?.messageIndex else {
            return nil
        }
        return NSNumber(value: index)
    }

    // MARK: - Backup Markers

    public var backupVersion: String! {
        get {
            storeAccount?.backupVersion
        } set {
            storeAccount?.backupVersion = newValue
        }
    }

    public func resetBackupMarkers() {
        inboundSessions.forEach { $0.backedUp = false }
    }

    public func markBackupDone(for sessions: [MXOlmInboundGroupSession]!) {
        for session in sessions {
            inboundSessions.filter({ $0.sessionId == session.session.sessionIdentifier() }).forEach { $0.backedUp = true }
        }
    }

    public func inboundGroupSessions(toBackup limit: UInt) -> [MXOlmInboundGroupSession]! {
        let toBackup = inboundSessions.filter { !$0.backedUp }
        if toBackup.isEmpty {
            return []
        }
        let toDrop = toBackup.count > limit ? toBackup.count - Int(limit) : 0
        return toBackup.dropLast(toDrop).map { $0.session }
    }

    public func inboundGroupSessionsCount(_ onlyBackedUp: Bool) -> UInt {
        UInt(onlyBackedUp ? inboundSessions.filter { $0.backedUp }.count : inboundSessions.count)
    }

    // MARK: - Outgoing Room Key Requests

    public func outgoingRoomKeyRequest(withRequestBody requestBody: [AnyHashable : Any]!) -> MXOutgoingRoomKeyRequest! {
        outgoingRoomKeyRequests.first(where: { NSDictionary(dictionary: $1.requestBody).isEqual(to: requestBody) })?.value
    }

    public func outgoingRoomKeyRequest(with state: MXRoomKeyRequestState) -> MXOutgoingRoomKeyRequest! {
        outgoingRoomKeyRequests.first(where: { $0.value.state == state })?.value
    }

    public func allOutgoingRoomKeyRequests(with state: MXRoomKeyRequestState) -> [MXOutgoingRoomKeyRequest]! {
        Array(outgoingRoomKeyRequests.filter { $1.state == state }.values)
    }

    public func allOutgoingRoomKeyRequests(withRoomId roomId: String!, sessionId: String!, algorithm: String!, senderKey: String!) -> [MXOutgoingRoomKeyRequest]! {
        Array(outgoingRoomKeyRequests.filter {
            $1.roomId == roomId
            && $1.sessionId == sessionId
            && $1.algorithm == algorithm
            && $1.senderKey == senderKey
        }.values)
    }

    public func store(_ request: MXOutgoingRoomKeyRequest!) {
        outgoingRoomKeyRequests[request.requestId] = request
    }

    public func update(_ request: MXOutgoingRoomKeyRequest!) {
        outgoingRoomKeyRequests[request.requestId] = request
    }

    public func deleteOutgoingRoomKeyRequest(withRequestId requestId: String!) {
        outgoingRoomKeyRequests.removeValue(forKey: requestId)
    }

    // MARK: - Incoming Room Key Requests

    public func store(_ request: MXIncomingRoomKeyRequest!) {
        incomingRoomKeyRequestsMap[request.requestId] = request
    }

    public func deleteIncomingRoomKeyRequest(_ requestId: String!, fromUser userId: String!, andDevice deviceId: String!) {
        let toBeRemoved = incomingRoomKeyRequestsMap.filter { $1.requestId == requestId && $1.userId == userId && $1.deviceId == deviceId }
        for identifier in toBeRemoved {
            incomingRoomKeyRequestsMap.removeValue(forKey: identifier.key)
        }
    }

    public func incomingRoomKeyRequest(withRequestId requestId: String!, fromUser userId: String!, andDevice deviceId: String!) -> MXIncomingRoomKeyRequest! {
        incomingRoomKeyRequestsMap.first(where: { $1.requestId == requestId && $1.userId == userId && $1.deviceId == deviceId })?.value
    }

    public func incomingRoomKeyRequests() -> MXUsersDevicesMap<NSArray>! {
        let result = MXUsersDevicesMap<NSMutableArray>()

        for request in incomingRoomKeyRequestsMap {
            if let requests = result.object(forDevice: request.value.deviceId, forUser: request.value.userId) {
                requests.add(request.value)
            } else {
                let requests = NSMutableArray(object: request.value)
                result.setObject(requests, forUser: request.value.userId, andDevice: request.value.deviceId)
            }
        }

        return result as? MXUsersDevicesMap<NSArray>
    }

    // MARK: - Secrets

    public func storeSecret(_ secret: String!, withSecretId secretId: String!) {
        secrets[secretId] = secret
    }

    public func secret(withSecretId secretId: String!) -> String! {
        secrets[secretId]
    }

    public func deleteSecret(withSecretId secretId: String!) {
        secrets.removeValue(forKey: secretId)
    }

    // MARK: - Blacklist Unverified Devices

    public var globalBlacklistUnverifiedDevices: Bool {
        get {
            storeAccount?.globalBlacklistUnverifiedDevices ?? false
        } set {
            storeAccount?.globalBlacklistUnverifiedDevices = newValue
        }
    }

    public func blacklistUnverifiedDevices(inRoom roomId: String!) -> Bool {
        algorithms[roomId]?.blacklistUnverifiedDevices ?? false
    }

    public func storeBlacklistUnverifiedDevices(inRoom roomId: String!, blacklist: Bool) {
        if let algorithm = algorithms[roomId] {
            algorithm.blacklistUnverifiedDevices = blacklist
        } else {
            algorithms[roomId] = RoomAlgorithm(algorithm: nil, blacklistUnverifiedDevices: blacklist)
        }
    }

    // MARK: - Crypto Version

    public var cryptoVersion: MXCryptoVersion {
        get {
            storeAccount?.cryptoVersion ?? .versionUndefined
        } set {
            storeAccount?.cryptoVersion = newValue
        }
    }

}

// MARK: - Models

// MARK: InboundSession

private class InboundSession {
    let session: MXOlmInboundGroupSession
    var backedUp: Bool

    var sessionId: String {
        session.session.sessionIdentifier()
    }

    init(session: MXOlmInboundGroupSession,
         backedUp: Bool = false) {
        self.session = session
        self.backedUp = backedUp
    }
}

// MARK: OlmSessionMapKey

private struct OlmSessionMapKey: Hashable {
    let sessionId: String
    let deviceKey: String
}

// MARK: Account

private struct Account {
    var userId: String?
    var deviceId: String?
    var cryptoVersion: MXCryptoVersion = .versionUndefined
    var deviceSyncToken: String?
    var olmAccount: OLMAccount?
    var backupVersion: String?
    var globalBlacklistUnverifiedDevices: Bool = false
    var deviceTrackingStatus: [String : NSNumber]?
}

// MARK: SharedOutboundSession

private struct SharedOutboundSession {
    let roomId: String
    let sessionId: String
    let device: MXDeviceInfo
    let messageIndex: UInt
}

// MARK: RoomAlgorithm

private class RoomAlgorithm {
    let algorithm: String?
    var blacklistUnverifiedDevices: Bool

    init(algorithm: String?,
         blacklistUnverifiedDevices: Bool = false) {
        self.algorithm = algorithm
        self.blacklistUnverifiedDevices = blacklistUnverifiedDevices
    }
}
// 
// 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

class MXForwardedRoomKeyEventContentUnitTests: XCTestCase {
    
    // MARK: - modelFromJSON
    
    func makeValidJSON() -> [String: Any] {
        return [
            "algorithm": "A",
            "room_id": "B",
            "sender_key": "C",
            "session_id": "D",
            "session_key": "E",
            "sender_claimed_ed25519_key": "F",
            "forwarding_curve25519_key_chain": ["G", "H"],
            kMXSharedHistoryKeyName: false
        ]
    }
    
    func test_modelFromJSON_doesNotCreateWithMissingFields() {
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: [:])
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "algorithm"))
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "room_id"))
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "sender_key"))
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "session_id"))
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "session_key"))
        )
        
        XCTAssertNil(MXForwardedRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "sender_claimed_ed25519_key"))
        )
    }
    
    func test_modelFromJSON_canCreateFromJSON() {
        let content = MXForwardedRoomKeyEventContent(fromJSON: makeValidJSON())
        
        XCTAssertNotNil(content)
        XCTAssertEqual(content?.algorithm, "A")
        XCTAssertEqual(content?.roomId, "B")
        XCTAssertEqual(content?.senderKey, "C")
        XCTAssertEqual(content?.sessionId, "D")
        XCTAssertEqual(content?.sessionKey, "E")
        XCTAssertEqual(content?.senderClaimedEd25519Key, "F")
        XCTAssertEqual(content?.forwardingCurve25519KeyChain, ["G", "H"])
        XCTAssertEqual(content?.sharedHistory, false)
    }
    
    func test_modelFromJSON_forwardingCurveChainDefaultsToEmpty() {
        let json = makeValidJSON().removing(key: "forwarding_curve25519_key_chain")
        let content = MXForwardedRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content?.forwardingCurve25519KeyChain, [])
    }
    
    func test_modelFromJSON_sharedHistory() {
        var json = makeValidJSON()
        
        json[kMXSharedHistoryKeyName] = true
        let content1 = MXForwardedRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content1?.sharedHistory, true)
        
        json[kMXSharedHistoryKeyName] = false
        let content2 = MXForwardedRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content2?.sharedHistory, false)
        
        json[kMXSharedHistoryKeyName] = nil
        let content3 = MXForwardedRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content3?.sharedHistory, false)
    }
    
    // MARK: - JSONDictionary
    
    func test_JSONDictionary_canExportJSON() {
        let content = MXForwardedRoomKeyEventContent()
        content.algorithm = "A"
        content.roomId = "B"
        content.senderKey = "C"
        content.sessionId = "D"
        content.sessionKey = "E"
        content.senderClaimedEd25519Key = "F"
        content.forwardingCurve25519KeyChain = ["G", "H"]
        content.sharedHistory = false
        
        let json = content.jsonDictionary()
        
        XCTAssertEqual(json as? NSDictionary, makeValidJSON() as NSDictionary)
    }
}
//
// 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

class MXRoomKeyEventContentUnitTests: XCTestCase {
    
    // MARK: - modelFromJSON
    
    func makeValidJSON() -> [String: Any] {
        return [
            "algorithm": "A",
            "room_id": "B",
            "session_id": "C",
            "session_key": "D",
            kMXSharedHistoryKeyName: false
        ]
    }
    
    func test_modelFromJSON_doesNotCreateWithMissingFields() {
        XCTAssertNil(MXRoomKeyEventContent(
            fromJSON: [:])
        )
        
        XCTAssertNil(MXRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "algorithm"))
        )
        
        XCTAssertNil(MXRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "room_id"))
        )
        
        XCTAssertNil(MXRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "session_id"))
        )
        
        XCTAssertNil(MXRoomKeyEventContent(
            fromJSON: makeValidJSON().removing(key: "session_key"))
        )
    }
    
    func test_modelFromJSON_canCreateFromJSON() {
        let content = MXRoomKeyEventContent(fromJSON: makeValidJSON())

        XCTAssertNotNil(content)
        XCTAssertEqual(content?.algorithm, "A")
        XCTAssertEqual(content?.roomId, "B")
        XCTAssertEqual(content?.sessionId, "C")
        XCTAssertEqual(content?.sessionKey, "D")
        XCTAssertEqual(content?.sharedHistory, false)
    }

    func test_modelFromJSON_sharedHistory() {
        var json = makeValidJSON()

        json[kMXSharedHistoryKeyName] = true
        let content1 = MXRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content1?.sharedHistory, true)

        json[kMXSharedHistoryKeyName] = false
        let content2 = MXRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content2?.sharedHistory, false)

        json[kMXSharedHistoryKeyName] = nil
        let content3 = MXRoomKeyEventContent(fromJSON: json)
        XCTAssertEqual(content3?.sharedHistory, false)
    }

    // MARK: - JSONDictionary

    func test_JSONDictionary_canExportJSON() {
        let content = MXRoomKeyEventContent()
        content.algorithm = "A"
        content.roomId = "B"
        content.sessionId = "C"
        content.sessionKey = "D"
        content.sharedHistory = false

        let json = content.jsonDictionary()

        XCTAssertEqual(json as? NSDictionary, makeValidJSON() as NSDictionary)
    }
}
// 
// 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

private let BobSenderKey = "BobDeviceCurveKey"

class MXUnrequestedForwardedRoomKeyManagerUnitTests: XCTestCase {
    class Delegate: MXUnrequestedForwardedRoomKeyManagerDelegate {
        var stubbedUserKeys = [String: [MXDeviceInfo]]()
        var spyKeys = [MXRoomKeyInfo]()
        
        func downloadDeviceKeys(userId: String, completion: @escaping (MXUsersDevicesMap<MXDeviceInfo>) -> Void) {
            let map = MXUsersDevicesMap<MXDeviceInfo>()
            for (userId, devices) in stubbedUserKeys {
                for device in devices {
                    map.setObject(device, forUser: userId, andDevice: device.deviceId)
                }
            }
            completion(map)
        }
        
        func acceptRoomKey(keyInfo: MXRoomKeyInfo) {
            spyKeys.append(keyInfo)
        }
    }
    
    class StubDateProvider: MXDateProviding {
        var stubbedDate: Date?
        func currentDate() -> Date {
            return stubbedDate ?? Date()
        }
    }
    
    var dateProvider: StubDateProvider!
    var manager: MXUnrequestedForwardedRoomKeyManager!
    var delegate: Delegate!
    
    override func setUp() {
        dateProvider = StubDateProvider()
        manager = MXUnrequestedForwardedRoomKeyManager(dateProvider: dateProvider)
        delegate = Delegate()
        manager.delegate = delegate
        
        delegate.stubbedUserKeys = [
            "Bob": [
                MXDeviceInfo(fromJSON: [
                    "user_id": "Bob",
                    "device_id": "BobDevice",
                    "keys": [
                        "ed25519:BobDevice": "BobDeviceEdKey",
                        "curve25519:BobDevice": BobSenderKey,
                    ]
                ])
            ]
        ]
    }
    
    func test_processUnrequestedKeys_doesNothingIfNoKeys() {
        manager.processUnrequestedKeys()
        XCTAssertEqual(delegate.spyKeys, [])
    }
    
    func test_processUnrequestedKeys_addsAllKeysForInvitedRooms() {
        let keys = [
            MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
            MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
            MXRoomKeyInfo.fixture(sessionId: "3", roomId: "B"),
            MXRoomKeyInfo.fixture(sessionId: "4", roomId: "C"),
        ]
        for info in keys {
            manager.addPendingKey(keyInfo: info, senderId: "Bob", senderKey: BobSenderKey)
        }
        manager.onRoomInvite(roomId: "A", senderId: "Bob")
        manager.onRoomInvite(roomId: "C", senderId: "Bob")
        
        manager.processUnrequestedKeys()
        
        let sessionIds = delegate.spyKeys.map { $0.sessionId }
        XCTAssertEqual(Set(sessionIds), ["1", "2", "4"])
    }
    
    func test_processUnrequestedKeys_addsOnlyKeysFromRoomInviter() {
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
            senderId: "Bob",
            senderKey: "AliceKey"
        )
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
            senderId: "Bob",
            senderKey: BobSenderKey
        )
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "3", roomId: "A"),
            senderId: "Bob",
            senderKey: "CharlieKey"
        )
        manager.onRoomInvite(roomId: "A", senderId: "Bob")

        manager.processUnrequestedKeys()

        let sessionIds = delegate.spyKeys.map { $0.sessionId }
        XCTAssertEqual(Set(sessionIds), ["2"])
    }
    
    func test_processUnrequestedKeys_doesNotAddKeysIfSenderNotValid() {
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
            senderId: "Bob",
            senderKey: "BobInvalidKey"
        )
        manager.onRoomInvite(roomId: "A", senderId: "Bob")

        manager.processUnrequestedKeys()

        XCTAssertEqual(delegate.spyKeys, [])
    }
    
    func test_processUnrequestedKeys_removesProcessedKeys() {
        delegate.spyKeys = []
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
            senderId: "Bob",
            senderKey: BobSenderKey
        )
        manager.processUnrequestedKeys()
        XCTAssertEqual(delegate.spyKeys.count, 0)
        
        delegate.spyKeys = []
        manager.onRoomInvite(roomId: "A", senderId: "Bob")
        manager.processUnrequestedKeys()
        XCTAssertEqual(delegate.spyKeys.count, 1)
        
        delegate.spyKeys = []
        manager.processUnrequestedKeys()
        XCTAssertEqual(delegate.spyKeys.count, 0)
    }
    
    func test_processUnrequestedKeys_removesInvitesMoreThan10MinutesFromPresent() {
        let minutesToRoom: [TimeInterval: String] = [
            -10: "A",
             -9: "B",
             0: "C",
             9: "D",
             10: "E"
        ]
        
        // First add all the keys
        for (minutes, roomId) in minutesToRoom {
            stubDate(timeInterval: minutes * 60)
            manager.addPendingKey(
                keyInfo: MXRoomKeyInfo.fixture(sessionId: roomId, roomId: roomId),
                senderId: "Bob",
                senderKey: BobSenderKey
            )
        }
        
        // Now add invites
        for (minutes, roomId) in minutesToRoom {
            stubDate(timeInterval: minutes * 60)
            manager.onRoomInvite(roomId: roomId, senderId: "Bob")
        }
        
        // Set the date to present
        stubDate(timeInterval: 0)
        
        // Process invites
        manager.processUnrequestedKeys()
        
        let sessionIds = delegate.spyKeys.map { $0.sessionId }
        XCTAssertEqual(Set(sessionIds), ["B", "C", "D"])
    }
    
    func test_processUnrequestedKeys_removesKeysOlderThan10MinutesOfInvite() {
        stubDate(timeInterval: 0)
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
            senderId: "Bob",
            senderKey: BobSenderKey
        )
        stubDate(timeInterval: 1 * 60)
        manager.addPendingKey(
            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
            senderId: "Bob",
            senderKey: BobSenderKey
        )
        stubDate(timeInterval: 10 * 60)
        manager.onRoomInvite(roomId: "A", senderId: "Bob")
        stubDate(timeInterval: 19 * 60)
        
        manager.processUnrequestedKeys()
        
        XCTAssertEqual(delegate.spyKeys.count, 1)
        XCTAssertEqual(delegate.spyKeys.first?.sessionId, "2")
    }
    
    // MARK: - Helpers
    
    func stubDate(timeInterval: TimeInterval) {
        dateProvider.stubbedDate = Date(timeIntervalSince1970: timeInterval)
    }
}

private extension MXRoomKeyInfo {
    static func fixture(
        sessionId: String,
        roomId: String
    ) -> MXRoomKeyInfo {
        return MXRoomKeyInfo(
            algorithm: "",
            sessionId: sessionId,
            sessionKey: "",
            roomId: roomId,
            senderKey: "",
            forwardingKeyChain: nil,
            keysClaimed: [:],
            exportFormat: false,
            sharedHistory: false
        )
    }
}
            sharedHistory: sharedHistory,
            untrusted: false
// 
// 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

class MXRoomKeyInfoFactoryUnitTests: XCTestCase {
    
    var store: MXMemoryCryptoStore!
    var factory: MXRoomKeyInfoFactory!
    override func setUp() {
        store = MXMemoryCryptoStore(credentials: MXCredentials(homeServer: "", userId: "Alice", accessToken: nil))
        factory = MXRoomKeyInfoFactory(myUserId: "Alice", store: store)
        storeOutgoingKeyRequest(requestId: "1")
    }
    
    // MARK: - Any event
    
    func test_roomKeyInfo_isNilForInvalidEvent() {
        let info = factory.roomKey(for: MXEvent(fromJSON: [:]))
        XCTAssertNil(info)
    }
    
    // MARK: - Room key event
    
    func test_roomKeyInfo_createFromRoomKeyEvent() {
        let event = MXEvent.roomKeyFixture()
        
        let key = factory.roomKey(for: event)
        
        let info = key?.info
        XCTAssertNotNil(key)
        XCTAssertEqual(key?.type, .safe)
        XCTAssertEqual(info?.algorithm, "megolm")
        XCTAssertEqual(info?.sessionId, "session1")
        XCTAssertEqual(info?.sessionKey, "<key>")
        XCTAssertEqual(info?.roomId, "!123:matrix.org")
        XCTAssertEqual(info?.senderKey, "<sender_key>")
        XCTAssertNil(info?.forwardingKeyChain)
        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
        XCTAssertEqual(info?.exportFormat, false)
        XCTAssertEqual(info?.sharedHistory, false)
    }
    
    // MARK: - Forwarded room key event
    
    func test_roomKeyInfo_isUnrequestedIfKeyNotRequested() {
        store.deleteOutgoingRoomKeyRequest(withRequestId: "1")
        storeDevice(userId: "Alice", trusted: true, identityKey: "AliceSender")
        let event = MXEvent.forwardedRoomKeyFixture(
            senderKey: "AliceSender"
        )
        
        let key = factory.roomKey(for: event)
        
        let info = key?.info
        XCTAssertNotNil(key)
        XCTAssertEqual(key?.type, .unrequested)
        XCTAssertEqual(info?.algorithm, "megolm")
        XCTAssertEqual(info?.sessionId, "session1")
        XCTAssertEqual(info?.sessionKey, "<key>")
        XCTAssertEqual(info?.roomId, "!123:matrix.org")
        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
        XCTAssertEqual(info?.exportFormat, true)
        XCTAssertEqual(info?.sharedHistory, false)
    }
    
    func test_roomKeyInfo_isUnsafeIfNotFromMyself() {
        storeDevice(userId: "Bob", trusted: true, identityKey: "AliceSender")
        let event = MXEvent.forwardedRoomKeyFixture(
            senderKey: "AliceSender"
        )
        
        let key = factory.roomKey(for: event)
        
        let info = key?.info
        XCTAssertNotNil(key)
        XCTAssertEqual(key?.type, .unsafe)
        XCTAssertEqual(info?.algorithm, "megolm")
        XCTAssertEqual(info?.sessionId, "session1")
        XCTAssertEqual(info?.sessionKey, "<key>")
        XCTAssertEqual(info?.roomId, "!123:matrix.org")
        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
        XCTAssertEqual(info?.exportFormat, true)
        XCTAssertEqual(info?.sharedHistory, false)
    }
    
    func test_roomKeyInfo_isUnsafeIfFromUnverifiedDevice() {
        storeDevice(userId: "Alice", trusted: false, identityKey: "AliceSender")
        let event = MXEvent.forwardedRoomKeyFixture(
            senderKey: "AliceSender"
        )
        
        let key = factory.roomKey(for: event)
        
        let info = key?.info
        XCTAssertNotNil(key)
        XCTAssertEqual(key?.type, .unsafe)
        XCTAssertEqual(info?.algorithm, "megolm")
        XCTAssertEqual(info?.sessionId, "session1")
        XCTAssertEqual(info?.sessionKey, "<key>")
        XCTAssertEqual(info?.roomId, "!123:matrix.org")
        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
        XCTAssertEqual(info?.exportFormat, true)
        XCTAssertEqual(info?.sharedHistory, false)
    }
    
    func test_roomKeyInfo_createFromForwardedRoomKeyEvent() {
        storeDevice(userId: "Alice", trusted: true, identityKey: "AliceSender")
        let event = MXEvent.forwardedRoomKeyFixture(
            senderKey: "AliceSender"
        )
        
        let key = factory.roomKey(for: event)
        
        let info = key?.info
        XCTAssertNotNil(key)
        XCTAssertEqual(key?.type, .safe)
        XCTAssertEqual(info?.algorithm, "megolm")
        XCTAssertEqual(info?.sessionId, "session1")
        XCTAssertEqual(info?.sessionKey, "<key>")
        XCTAssertEqual(info?.roomId, "!123:matrix.org")
        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
        XCTAssertEqual(info?.exportFormat, true)
        XCTAssertEqual(info?.sharedHistory, false)
    }
    
    // MARK: - Helpers
    
    func storeOutgoingKeyRequest(
        requestId: String = "1",
        algorithm: String = "megolm",
        roomId: String = "!123:matrix.org",
        sessionId: String = "session1",
        senderKey: String = "<initial_sender_key>"
    ) {
        let request = MXOutgoingRoomKeyRequest()
        request.requestId = requestId
        
        request.requestBody = [
            "room_id": roomId,
            "algorithm": algorithm,
            "sender_key": senderKey,
            "session_id": sessionId
        ]
        store.store(request)
    }
    
    func storeDevice(userId: String, trusted: Bool, identityKey: String) {
        let trust = MXDeviceTrustLevel(
            localVerificationStatus: trusted ? .verified : .unverified,
            crossSigningVerified: false
        )
        let device = MXDeviceInfo(fromJSON: [
            "user_id": userId,
            "device_id": "ABC",
            "keys": ["curve25519:ABC": identityKey]
        ])!
        device.setValue(trust, forKey: "trustLevel")
        store.storeDevice(forUser: userId, device: device)
    }
}
    
    static func roomKeyFixture(
        algorithm: String = "megolm",
        roomId: String = "!123:matrix.org",
        sessionId: String = "session1",
        sessionKey: String = "<key>",
        senderKey: String = "<sender_key>",
        claimedKey: String = "<claimed_key>",
        sharedHistory: Bool? = nil
    ) -> MXEvent {
        var content: [String: Any] = [
            "type": kMXEventTypeStringRoomKey,
            "room_id": roomId,
            "session_id": sessionId,
            "session_key": sessionKey,
            "algorithm": algorithm
        ]
        
        if let sharedHistory = sharedHistory {
            content["org.matrix.msc3061.shared_history"] = sharedHistory
        }
        
        let result = MXEventDecryptionResult()
        result.senderCurve25519Key = senderKey
        result.claimedEd25519Key = claimedKey
        result.clearEvent = [
            "type": kMXEventTypeStringRoomKey,
            "content": content
        ]
        
        let event = MXEvent(fromJSON: [:])!
        event.setClearData(result)
        return event
    }
    
    static func forwardedRoomKeyFixture(
        algorithm: String = "megolm",
        roomId: String = "!123:matrix.org",
        sessionId: String = "session1",
        sessionKey: String = "<key>",
        senderKey: String = "<sender_key>",
        initialSenderKey: String = "<initial_sender_key>",
        claimedKey: String = "<claimed_key>",
        sharedHistory: Bool = false
    ) -> MXEvent {
        let content: [String: Any] = [
            "type": kMXEventTypeStringRoomKey,
            "room_id": roomId,
            "session_id": sessionId,
            "session_key": sessionKey,
            "algorithm": algorithm,
            "sender_key": initialSenderKey,
            "sender_claimed_ed25519_key": claimedKey,
            kMXSharedHistoryKeyName: sharedHistory
        ]
        
        let result = MXEventDecryptionResult()
        result.senderCurve25519Key = senderKey
        result.claimedEd25519Key = claimedKey
        result.clearEvent = [
            "type": kMXEventTypeStringRoomForwardedKey,
            "content": content
        ]
        
        let event = MXEvent(fromJSON: [:])!
        event.setClearData(result)
        return event
    }
 - Alice trusts the new device and vice versa
            // - Alice trusts the new device and vice versa
            [newAliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:aliceSession.myDeviceId ofUser:aliceSession.myUserId success:nil failure:nil];
#import "MXLRUCache.h"
                MXLRUCache *cache = [bobSession.crypto.olmDevice valueForKey:@"inboundGroupSessionCache"];
                [cache clear];
#import "MatrixSDKSwiftHeader.h"
#pragma mark - Supported To-Device events

- (void)testSupportedToDeviceEvents
{
    MXEvent *event1 = [MXEvent modelFromJSON:@{
        @"type": @"m.room.encrypted",
        @"content": @{
            @"algorithm": kMXCryptoOlmAlgorithm
        }
    }];
    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event1]);
    
    MXEvent *event2 = [MXEvent modelFromJSON:@{
        @"type": @"m.room.message",
    }];
    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event2]);
    
    MXEvent *event3 = [MXEvent modelFromJSON:@{
        @"type": @"random",
    }];
    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event3]);
}

- (void)testUnsupportedToDeviceEvents
{
    MXEvent *event1 = [MXEvent modelFromJSON:@{
        @"type": @"m.room.encrypted",
        @"content": @{
            @"algorithm": kMXCryptoMegolmAlgorithm
        }
    }];
    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event1]);
    
    MXEvent *event2 = [MXEvent modelFromJSON:@{
        @"type": @"m.room_key",
    }];
    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event2]);
    
    MXEvent *event3 = [MXEvent modelFromJSON:@{
        @"type": @"m.forwarded_room_key",
    }];
    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event3]);
    
    MXEvent *event4 = [MXEvent modelFromJSON:@{
        @"type": @"m.secret.send",
    }];
    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event4]);
}


        "MXForwardedRoomKeyEventContentUnitTests",
        "MXKeyVerificationRequestV2UnitTests",
        "MXRoomKeyEventContentUnitTests",
        "MXRoomKeyInfoFactoryUnitTests",
        "MXSASTransactionV2UnitTests",
        "MXTrustLevelSourceUnitTests",
        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
        "MXForwardedRoomKeyEventContentUnitTests",
        "MXKeyVerificationRequestV2UnitTests",
        "MXRoomKeyEventContentUnitTests",
        "MXRoomKeyInfoFactoryUnitTests",
        "MXSASTransactionV2UnitTests",
        "MXTrustLevelSourceUnitTests",
        "MXUnrequestedForwardedRoomKeyManagerUnitTests",