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 { 0 } func loadSignedPreKey(withId: UInt32) -> Data? { print(withId) return nil } func storeSignedPreKey(_ data: Data, withId: UInt32) -> Bool { print(data, withId) return false } func containsSignedPreKey(withId: UInt32) -> Bool { print(withId) return false } func deleteSignedPreKey(withId: UInt32) -> Bool { print(withId) return false } } // MARK: - Identity extension ClientMartinOMEMO: SignalIdentityKeyStoreProtocol { func keyPair() -> (any MartinOMEMO.SignalIdentityKeyPairProtocol)? { 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 } }