import Foundation import GRDB import Martin import MartinOMEMO final class ClientMartinOMEMO { let credentials: Credentials init(_ credentials: Credentials) { self.credentials = credentials } var signal: (SignalStorage, SignalContext) { let signalStorage = SignalStorage(sessionStore: self, preKeyStore: self, signedPreKeyStore: self, identityKeyStore: self, senderKeyStore: self) // swiftlint:disable:next force_unwrapping let signalContext = SignalContext(withStorage: signalStorage)! signalStorage.setup(withContext: signalContext) return (signalStorage, signalContext) } } // MARK: - Session extension ClientMartinOMEMO: SignalSessionStoreProtocol { func sessionRecord(forAddress address: MartinOMEMO.SignalAddress) -> Data? { do { let data = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_sessions WHERE account = :account AND name = :name AND device_id = :deviceId", arguments: ["account": credentials.bareJid, "name": address.name, "deviceId": address.deviceId] ) } return data?["key"] } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return nil } } func allDevices(for name: String, activeAndTrusted: Bool) -> [Int32] { do { let sql = activeAndTrusted ? """ SELECT s.device_id FROM omemo_sessions s LEFT JOIN omemo_identities i ON s.account = i.account AND s.name = i.name AND s.device_id = i.device_id WHERE s.account = :account AND s.name = :name AND ((i.status >= 0 AND i.status % 2 = 0) OR i.status IS NULL) """ : "SELECT device_id FROM omemo_sessions WHERE account = :account AND name = :name" let data = try Database.shared.dbQueue.read { db in try Row.fetchAll( db, sql: sql, arguments: ["account": credentials.bareJid, "name": name] ) } return data.map { $0["device_id"] } } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return [] } } func storeSessionRecord(_ data: Data, forAddress: MartinOMEMO.SignalAddress) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "INSERT INTO omemo_sessions (account, name, device_id, key) VALUES (:account, :name, :deviceId, :key)", arguments: ["account": credentials.bareJid, "name": forAddress.name, "deviceId": forAddress.deviceId, "key": data] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func containsSessionRecord(forAddress: MartinOMEMO.SignalAddress) -> Bool { do { let rec = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_sessions WHERE account = :account AND name = :name AND device_id = :deviceId", arguments: ["account": credentials.bareJid, "name": forAddress.name, "deviceId": forAddress.deviceId] ) } return rec != nil } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func deleteSessionRecord(forAddress: MartinOMEMO.SignalAddress) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_sessions WHERE account = :account AND name = :name AND device_id = :deviceId", arguments: ["account": credentials.bareJid, "name": forAddress.name, "deviceId": forAddress.deviceId] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func deleteAllSessions(for name: String) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_sessions WHERE account = :account AND name = :name", arguments: ["account": credentials.bareJid, "name": name] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func sessionsWipe() { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_sessions WHERE account = :account", arguments: ["account": credentials.bareJid] ) } } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") } } } // MARK: - PreKey extension ClientMartinOMEMO: SignalPreKeyStoreProtocol { func currentPreKeyId() -> UInt32 { do { let data = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT max(id) FROM omemo_pre_keys WHERE account = :account", arguments: ["account": credentials.bareJid] ) } return data?["id"] ?? 0 } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return 0 } } func loadPreKey(withId: UInt32) -> Data? { do { let data = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return data?["key"] } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return nil } } func storePreKey(_ data: Data, withId: UInt32) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "INSERT INTO omemo_pre_keys (account, id, key) VALUES (:account, :id, :key)", arguments: ["account": credentials.bareJid, "id": withId, "key": data] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func containsPreKey(withId: UInt32) -> Bool { do { let rec = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return rec != nil } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func deletePreKey(withId: UInt32) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } // TODO: Check logic of this function carefully!!! func flushDeletedPreKeys() -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: """ DELETE FROM omemo_pre_keys WHERE account = :account AND id IN ( SELECT id FROM omemo_pre_keys WHERE account = :account AND id NOT IN ( SELECT id FROM omemo_pre_keys WHERE account = :account ORDER BY id DESC LIMIT 100) ) """, arguments: ["account": credentials.bareJid] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func preKeysWipe() { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_pre_keys WHERE account = :account", arguments: ["account": credentials.bareJid] ) } } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") } } } // MARK: - SignedPreKey extension ClientMartinOMEMO: SignalSignedPreKeyStoreProtocol { func countSignedPreKeys() -> Int { do { let data = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT count(1) FROM omemo_signed_pre_keys WHERE account = :account", arguments: ["account": credentials.bareJid] ) } return data?["count(1)"] ?? 0 } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return 0 } } func loadSignedPreKey(withId: UInt32) -> Data? { do { let data = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_signed_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return data?["key"] } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return nil } } func storeSignedPreKey(_ data: Data, withId: UInt32) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "INSERT INTO omemo_signed_pre_keys (account, id, key) VALUES (:account, :id, :key)", arguments: ["account": credentials.bareJid, "id": withId, "key": data] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func containsSignedPreKey(withId: UInt32) -> Bool { do { let rec = try Database.shared.dbQueue.read { db in try Row.fetchOne( db, sql: "SELECT key FROM omemo_signed_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return rec != nil } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func deleteSignedPreKey(withId: UInt32) -> Bool { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_signed_pre_keys WHERE account = :account AND id = :id", arguments: ["account": credentials.bareJid, "id": withId] ) } return true } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") return false } } func wipeSignedPreKeys() { do { try Database.shared.dbQueue.write { db in try db.execute( sql: "DELETE FROM omemo_signed_pre_keys WHERE account = :account", arguments: ["account": credentials.bareJid] ) } } catch { logIt(.error, "Error fetching chats: \(error.localizedDescription)") } } } // MARK: - Identity extension ClientMartinOMEMO: SignalIdentityKeyStoreProtocol { // func regenerateKeys(wipe: Bool = false) -> Bool { // if wipe { // wipeSignedPreKeys() // } // } // if wipe { // DBOMEMOStore.instance.wipe(forAccount: context!.sessionObject.userBareJid!) // } // // let hasKeyPair = identityKeyStore.keyPair() != nil // if wipe || identityKeyStore.localRegistrationId() == 0 || !hasKeyPair { // let regId: UInt32 = signalContext.generateRegistrationId() // AccountSettings.omemoRegistrationId(for: context!.sessionObject.userBareJid!, value: regId) // // let keyPair = SignalIdentityKeyPair.generateKeyPair(context: signalContext) // if !identityKeyStore.save(identity: SignalAddress(name: context!.sessionObject.userBareJid!.stringValue, deviceId: Int32(identityKeyStore.localRegistrationId())), key: keyPair) {} // } // return true // } func keyPair() -> (any MartinOMEMO.SignalIdentityKeyPairProtocol)? { // guard let deviceId = localRegistrationId(forAccount: account) else { // return nil // } // // guard // let data = try! Database.main.reader({ database in // try database.select(query: .omemoKeyPairForAccount, params: ["account": account, "name": account.stringValue, "deviceId": deviceId]).mapFirst { $0.data(for: "key") } // }) // else { // return nil // } // // return SignalIdentityKeyPair(fromKeyPairData: data) nil } func localRegistrationId() -> UInt32 { 0 } func save(identity: MartinOMEMO.SignalAddress, key: (any MartinOMEMO.SignalIdentityKeyProtocol)?) -> Bool { print(identity, key) return false } func isTrusted(identity: MartinOMEMO.SignalAddress, key: (any MartinOMEMO.SignalIdentityKeyProtocol)?) -> Bool { print(identity, key) return false } func save(identity: MartinOMEMO.SignalAddress, publicKeyData: Data?) -> Bool { print(identity, publicKeyData) return false } func isTrusted(identity: MartinOMEMO.SignalAddress, publicKeyData: Data?) -> Bool { print(identity, publicKeyData) return false } func setStatus(_ status: MartinOMEMO.IdentityStatus, forIdentity: MartinOMEMO.SignalAddress) -> Bool { print(status, forIdentity) return false } func setStatus(active: Bool, forIdentity: MartinOMEMO.SignalAddress) -> Bool { print(active, forIdentity) return false } func identities(forName: String) -> [MartinOMEMO.Identity] { print(forName) return [] } func identityFingerprint(forAddress address: MartinOMEMO.SignalAddress) -> String? { print(address) return nil } } // MARK: - SenderKey extension ClientMartinOMEMO: SignalSenderKeyStoreProtocol { func storeSenderKey(_ key: Data, address: MartinOMEMO.SignalAddress?, groupId: String?) -> Bool { print(key, address, groupId) return false } func loadSenderKey(forAddress: MartinOMEMO.SignalAddress?, groupId: String?) -> Data? { print(forAddress, groupId) return nil } }