512 lines
18 KiB
Swift
512 lines
18 KiB
Swift
import Foundation
|
|
import GRDB
|
|
import Martin
|
|
import MartinOMEMO
|
|
|
|
final class ClientMartinOMEMO {
|
|
let credentials: Credentials
|
|
|
|
private let queue = DispatchQueue(label: "SignalPreKeyRemovalQueue")
|
|
private var preKeysMarkedForRemoval: [UInt32] = []
|
|
|
|
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)
|
|
}
|
|
// 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
|
|
// }
|
|
}
|
|
|
|
// 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 {
|
|
queue.async {
|
|
print("queueing prekey with id \(withId) for removal..")
|
|
self.preKeysMarkedForRemoval.append(withId)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// TODO: Check logic of this function carefully!!!
|
|
func flushDeletedPreKeys() -> Bool {
|
|
false
|
|
// !queue.sync { () -> [UInt32] in
|
|
// defer {
|
|
// preKeysMarkedForRemoval.removeAll()
|
|
// }
|
|
// print("removing queued prekeys: \(preKeysMarkedForRemoval)")
|
|
// do {
|
|
// Database.shared.dbQueue.write { db in
|
|
// try db.execute(
|
|
// sql: "DETLETE FROM omemo_pre_keys WHERE account = :account AND id IN (:ids)",
|
|
// arguments: ["account": credentials.bareJid, "ids": preKeysMarkedForRemoval]
|
|
// )
|
|
// }
|
|
// } catch {
|
|
// logIt(.error, "Error fetching chats: \(error.localizedDescription)")
|
|
// return [0]
|
|
// }
|
|
//
|
|
// // return preKeysMarkedForRemoval.filter { id in DBOMEMOStore.instance.deletePreKey(forAccount: context!.sessionObject.userBareJid!, withId: id) }
|
|
// }.isEmpty
|
|
//
|
|
//
|
|
//
|
|
// 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 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 save(identity: MartinOMEMO.SignalAddress, publicKeyData: Data?) -> Bool {
|
|
print(identity, publicKeyData)
|
|
return false
|
|
}
|
|
|
|
func isTrusted(identity _: MartinOMEMO.SignalAddress, key _: (any MartinOMEMO.SignalIdentityKeyProtocol)?) -> Bool {
|
|
true
|
|
}
|
|
|
|
func isTrusted(identity _: MartinOMEMO.SignalAddress, publicKeyData _: Data?) -> Bool {
|
|
true
|
|
}
|
|
|
|
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 name: String) -> [MartinOMEMO.Identity] {
|
|
do {
|
|
return try Database.shared.dbQueue.read { db in
|
|
try Row.fetchAll(
|
|
db,
|
|
sql: "SELECT * FROM omemo_identities WHERE account = :account AND name = :name",
|
|
arguments: ["account": credentials.bareJid, "name": name]
|
|
)
|
|
}.compactMap { row in
|
|
guard
|
|
let fingerprint = row["fingerprint"] as? String,
|
|
let statusInt = row["status"] as? Int,
|
|
let status = MartinOMEMO.IdentityStatus(rawValue: statusInt),
|
|
let deviceId = row["device_id"] as? Int32,
|
|
let own = row["own"] as? Int,
|
|
let key = row["key"] as? Data
|
|
else {
|
|
return nil
|
|
}
|
|
return MartinOMEMO.Identity(address: MartinOMEMO.SignalAddress(name: name, deviceId: deviceId), status: status, fingerprint: fingerprint, key: key, own: own > 0)
|
|
}
|
|
} catch {
|
|
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
|
|
return []
|
|
}
|
|
}
|
|
|
|
func identityFingerprint(forAddress address: MartinOMEMO.SignalAddress) -> String? {
|
|
do {
|
|
let data = try Database.shared.dbQueue.read { db in
|
|
try Row.fetchOne(
|
|
db,
|
|
sql: "SELECT fingerprint FROM omemo_identities WHERE account = :account AND name = :name AND device_id = :deviceId",
|
|
arguments: ["account": credentials.bareJid, "name": address.name, "deviceId": address.deviceId]
|
|
)
|
|
}
|
|
return data?["fingerprint"]
|
|
} catch {
|
|
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - SenderKey
|
|
extension ClientMartinOMEMO: SignalSenderKeyStoreProtocol {
|
|
func storeSenderKey(_: Data, address _: MartinOMEMO.SignalAddress?, groupId _: String?) -> Bool {
|
|
false
|
|
}
|
|
|
|
func loadSenderKey(forAddress _: MartinOMEMO.SignalAddress?, groupId _: String?) -> Data? {
|
|
nil
|
|
}
|
|
}
|