Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Controllers / OTROMEMOStorageManager.swift @ 8d76e2e3

History | View | Annotate | Download (7.87 KB)

1
//
2
//  OTROMEMOStorageCoordinator.swift
3
//  ChatSecure
4
//
5
//  Created by David Chiles on 9/15/16.
6
//  Copyright © 2016 Chris Ballinger. All rights reserved.
7
//
8

    
9
import Foundation
10
import YapDatabase
11

    
12
/**
13
 * Storage for XMPP-OMEMO 
14
 * This class handles storage of devies as it relates to our and our buddies device(s).
15
 * Create one per XMPP account.
16
 */
17
open class OTROMEMOStorageManager {
18
    let databaseConnection:YapDatabaseConnection
19
    let accountKey:String
20
    let accountCollection:String
21
    
22
    /**
23
     Create an OTROMEMOStorageManager.
24
     
25
     - parameter accountKey: The yap account key
26
     - parameter accountCollection: They yap account collection
27
     - parameter databaseConnection: The yap Datatbase connection to perform all the saves and gets from.
28
     */
29
    init(accountKey:String, accountCollection:String, databaseConnection:YapDatabaseConnection) {
30
        self.accountKey = accountKey
31
        self.accountCollection = accountCollection
32
        self.databaseConnection = databaseConnection
33
    }
34
    
35
    /**
36
     Convenience method that uses the class database connection.
37
     Retrievs all the devices for a given yap key and collection. Could be either for a buddy or an account.
38
     
39
     - parameter yapKey: The yap key for the account or buddy
40
     - parameter yapCollection: The yap collection for the account or buddy
41
     
42
     - returns: An Array of OTROMEMODevices. If there are no devices the array will be empty.
43
     **/
44
    open func getDevicesForParentYapKey(_ yapKey:String, yapCollection:String, trusted:Bool?) -> [OTROMEMODevice] {
45
        var result:[OTROMEMODevice]?
46
        self.databaseConnection.read { (transaction) in
47
            if let trust = trusted {
48
                result = OTROMEMODevice.allDevices(forParentKey: yapKey, collection: yapCollection, trusted: trust, transaction: transaction)
49
            } else {
50
                result = OTROMEMODevice.allDevices(forParentKey: yapKey, collection: yapCollection, transaction: transaction)
51
            }
52
        }
53
        return result ?? [OTROMEMODevice]();
54
    }
55
    
56
    /**
57
     Uses the class account key and collection to get all devices.
58
     
59
     - returns: An Array of OTROMEMODevices. If there are no devices the array will be empty.
60
     */
61
    open func getDevicesForOurAccount(_ trusted:Bool?) -> [OTROMEMODevice] {
62
        return self.getDevicesForParentYapKey(self.accountKey, yapCollection: self.accountCollection, trusted: trusted)
63
    }
64
    
65
    /**
66
     Uses the class account key and collection to get all devices for a given bare JID. Uses the class database connection.
67
     
68
     - parameter username: The bare JID for the buddy.
69
     
70
     - returns: An Array of OTROMEMODevices. If there are no devices the array will be empty.
71
     */
72
    open func getDevicesForBuddy(_ username:String, trusted:Bool?) -> [OTROMEMODevice] {
73
        guard let jid = XMPPJID(string: username) else { return [] }
74
        var result: [OTROMEMODevice] = []
75
        self.databaseConnection.read { (transaction) in
76
            if let buddy = OTRXMPPBuddy.fetchBuddy(jid: jid, accountUniqueId: self.accountKey, transaction: transaction) {
77
                if let trust = trusted {
78
                    result = OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: OTRBuddy.collection, trusted: trust, transaction: transaction)
79
                } else {
80
                    result = OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: OTRBuddy.collection, transaction: transaction)
81
                }
82
            }
83
        }
84
        return result;
85
    }
86
    
87
    /**
88
     Store devices for a yap key/collection
89
     
90
     - parameter devices: An array of the device numbers. Should be UInt32.
91
     - parameter parentYapKey: The yap key to attach the device to
92
     - parameter parentYapCollection: the yap collection to attach the device to
93
     - parameter transaction: the database transaction to perform the saves on
94
     */
95
    fileprivate func storeDevices(_ devices:[NSNumber], parentYapKey:String, parentYapCollection:String, transaction:YapDatabaseReadWriteTransaction) {
96
        
97
        let previouslyStoredDevices = OTROMEMODevice.allDevices(forParentKey: parentYapKey, collection: parentYapCollection, transaction: transaction)
98
        let previouslyStoredDevicesIds = previouslyStoredDevices.map({ (device) -> NSNumber in
99
            return device.deviceId
100
        })
101
        let previouslyStoredDevicesIdSet = Set(previouslyStoredDevicesIds)
102
        let newDeviceSet = Set(devices)
103
        
104
        if (devices.count == 0) {
105
            // Remove all devices
106
            previouslyStoredDevices.forEach({ (device) in
107
                device.remove(with: transaction)
108
            })
109
        } else if (previouslyStoredDevicesIdSet != newDeviceSet) {
110
            //New Devices to be saved and list to be reworked
111
            let devicesToRemove:Set<NSNumber> = previouslyStoredDevicesIdSet.subtracting(newDeviceSet)
112
            let devicesToAdd:Set<NSNumber> = newDeviceSet.subtracting(previouslyStoredDevicesIdSet)
113
            
114
            // Instead of fulling removing devices, mark them as removed for historical purposes
115
            devicesToRemove.forEach({ (deviceId) in
116
                let deviceKey = OTROMEMODevice.yapKey(withDeviceId: deviceId, parentKey: parentYapKey, parentCollection: parentYapCollection)
117
                guard var device = transaction.object(forKey: deviceKey, inCollection: OTROMEMODevice.collection) as? OTROMEMODevice else {
118
                    return
119
                }
120
                device = device.copy() as! OTROMEMODevice
121
                device.trustLevel = .removed
122
                transaction.setObject(device, forKey: device.uniqueId, inCollection: OTROMEMODevice.collection)
123
            })
124
            
125
            devicesToAdd.forEach({ (deviceId) in
126
                
127
                var trustLevel = OMEMOTrustLevel.untrustedNew
128
                if (previouslyStoredDevices.count == 0) {
129
                    //This is the first time we're seeing a device list for this account/buddy so it should be saved as TOFU
130
                    trustLevel = .trustedTofu
131
                }
132
                
133
                let newDevice = OTROMEMODevice(deviceId: deviceId, trustLevel:trustLevel, parentKey: parentYapKey, parentCollection: parentYapCollection, publicIdentityKeyData: nil, lastSeenDate:Date())
134
                newDevice.save(with: transaction)
135
            })
136
            
137
        }
138
    }
139
    
140
    /**
141
     Store devices for this account. These should come from the OMEMO device-list
142
     
143
     - parameter devices: An array of the device numbers. Should be UInt32.
144
     */
145
    open func storeOurDevices(_ devices:[NSNumber]) {
146
        self.databaseConnection.asyncReadWrite { (transaction) in
147
            self.storeDevices(devices, parentYapKey: self.accountKey, parentYapCollection: self.accountCollection, transaction: transaction)
148
        }
149
    }
150
    
151
    /**
152
     Store devices for a buddy connected to this account. These should come from the OMEMO device-list
153
     
154
     - parameter devices: An array of the device numbers. Should be UInt32.
155
     - parameter buddyUsername: The bare JID for the buddy.
156
     */
157
    open func storeBuddyDevices(_ devices:[NSNumber], buddyUsername:String, completion:(()->Void)?) {
158
        self.databaseConnection.asyncReadWrite { (transaction) in
159
            // Fetch the buddy from the database.
160
            guard let jid = XMPPJID(string: buddyUsername), let buddy = OTRXMPPBuddy.fetchBuddy(jid: jid, accountUniqueId: self.accountKey, transaction: transaction) else {
161
                // If this is teh first launch the buddy will not be in the buddy list becuase the roster comes in after device list from PEP.
162
                DDLogWarn("Could not find buddy to store devices \(buddyUsername)")
163
                return
164
            }
165
            self.storeDevices(devices, parentYapKey: buddy.uniqueId, parentYapCollection: buddy.threadCollection, transaction: transaction)
166
            completion?()
167
        }
168
    }
169
}